{"id":13514970,"url":"https://github.com/heywhy/elixir-request-validator","last_synced_at":"2025-08-21T12:30:35.585Z","repository":{"id":55484547,"uuid":"300994146","full_name":"heywhy/elixir-request-validator","owner":"heywhy","description":"A blazing fast request validator for your phoenix app, which validates a request body before hitting the request handler in the controller.","archived":false,"fork":false,"pushed_at":"2024-09-25T13:16:13.000Z","size":108,"stargazers_count":10,"open_issues_count":2,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-10T10:02:53.676Z","etag":null,"topics":["ecto-support","elixir","phoenix","phoenix-elixir","phoenix-framework","requestvalidator"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heywhy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-03T22:34:02.000Z","updated_at":"2024-09-25T13:16:17.000Z","dependencies_parsed_at":"2024-10-31T21:01:57.440Z","dependency_job_id":null,"html_url":"https://github.com/heywhy/elixir-request-validator","commit_stats":{"total_commits":71,"total_committers":5,"mean_commits":14.2,"dds":0.07042253521126762,"last_synced_commit":"ccf4bd7a32af105775b574ec33065c8f08c99f60"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heywhy%2Felixir-request-validator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heywhy%2Felixir-request-validator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heywhy%2Felixir-request-validator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heywhy%2Felixir-request-validator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heywhy","download_url":"https://codeload.github.com/heywhy/elixir-request-validator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230511479,"owners_count":18237657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecto-support","elixir","phoenix","phoenix-elixir","phoenix-framework","requestvalidator"],"created_at":"2024-08-01T05:01:04.704Z","updated_at":"2024-12-19T23:14:39.668Z","avatar_url":"https://github.com/heywhy.png","language":"Elixir","funding_links":[],"categories":["Elixir"],"sub_categories":[],"readme":"# RequestValidator\n\nA blazing fast request validator for your phoenix app, which validates a request body before hitting the request handler in the controller.\n\n- [Why RequestValidator](#why-request-validator)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Ecto Support](#ecto-support)\n- [Custom Validation Rules](#custom-validation-rules)\n\n## Why RequestValidator\n\nIt's common for a web service to validate incoming data. The most common layer where this is done is at the controller level and this sometimes leads to having a bloated controller. But with RequestValidator, you move the validation logic to a layer just before the controller and your controller is now free from doing validation.\n\n## Installation\n\nThe package can be installed by adding `request_validator` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:request_validator, \"~\u003e 0.8.5\"}\n  ]\nend\n```\n\n## Usage\n\nFirst of all, you need to define a validation schema to be used against the incoming data.\n\n```elixir\ndefmodule App.Requests.Registration do\n  use Request.Validator\n  \n  # Get the validation rules that apply to the incoming request.\n  @impl Request.Validator\n  def rules(_) do\n    [\n      email: ~w[required email]a,\n      first_name: ~w[required string]a,\n      last_name: ~w[required string]a,\n      password: [:required, :string, {:min, 8}, :confirmed]\n    ]\n  end\n\n  # Determine if the user is authorized to make this request.\n  @impl Request.Validator\n  def authorize(_), do: true\nend\n```\n\nThe above validation schema can now be used;\n\n```elixir\ndefmodule App.UserController do\n  use AppWeb, :controller\n\n  plug Request.Validator.Plug,\n    register: App.Requests.Registration\n\n  def register(conn, params) do\n    case App.UserService.create(params) do\n      :ok -\u003e\n        conn\n        |\u003e put_status(201)\n        |\u003e json(%{message: \"Account created successfully\"})\n      {:error, msg} -\u003e\n        conn\n        |\u003e put_status(500)\n        |\u003e json(%{message: msg})\n    end\n  end\nend\n```\n\nAs you can see in the controller, the `register` handler does not need to worry about validating the incoming request because `RequestValidator` will handle that automatically and send the right response if the request fails validation based on the given validation schema.\n\nYou can specify validation schema for each of the handlers in a controller:\n\n```elixir\ndefmodule App.UserController do\n  use AppWeb, :controller\n\n  plug Request.Validator.Plug,\n    login: App.Requests.Login,\n    register: App.Requests.Registration\n\n  ...\nend\n```\n\nFull documentation can be found at [https://hexdocs.pm/request_validator](https://hexdocs.pm/request_validator).\n\n## Ecto Support\n\nIn some cases, your application already makes use of the ecto library. I'm glad to tell you that this library has support when the rule method returns an `Ecto.Changeset` struct so that you can\nmake use of the advantages provided by this library without rewriting your validation logic. See the example below:\n\n```elixir\ndefmodule App.SomeEctoSchema do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  embedded_schema do\n    field(:name, :string)\n    field(:email, :string)\n    field(:age, :integer)\n    field(:password, :string)\n  end\n\n  @doc false\n  def changeset(attrs), do: changeset(%__MODULE__{}, attrs)\n\n  @doc false\n  def changeset(struct, attrs) do\n    struct\n    |\u003e cast(attrs, [:name, :email, :age, :password])\n    |\u003e validate_required([:name, :email, :age, :password])\n    |\u003e validate_number(:age, less_than_or_equal_to: 32)\n  end\nend\n\ndefmodule App.Requests.TestRequest do\n  use Request.Validator\n\n  alias App.SomeEctoSchema\n\n  @impl Request.Validator\n  def rules(conn), do: SomeEctoSchema.changeset(conn.params)\n\n  @impl Request.Validator\n  def authorize(_), do: true\nend\n```\n\n## Custom Validation Rules\n\nThis library provides a variety of helpful rules, however, you might want to define some rules to house your validation logic. To achieve this, you need to create your own rules module, extend the default rules and update the library configuration;\n\n```elixir\ndefmodule App.Validation.Rules do\n  use Request.Validator.Rules # grab default rules provided by the library\n\n  @spec uppercase(binary(), keyword()) :: :ok | {:error, binary()}\n  def uppercase(value, fields: _fields, field: _field) do\n    case String.upcase(value) do\n      ^value -\u003e\n        :ok\n      _ -\u003e\n        {:error, \"This field must be uppercase.\"}\n    end\n  end\nend\n```\n\nAfter defining a module with your custom rules, you will need to update your application configuration:\n\n```elixir\nconfig :request_validator, rules_module: App.Validation.Rules\n```\n\nOnce the new rule has been added and configuration updated, it can now be used:\n\n```elixir\n# ...\n  def rules(_) do\n    [\n      name: ~w[required string uppercase]a\n    ]\n  end\n# ...\n```\n\nNote that if your rule accepts options/parameters, its function definition should have an arity of 3, and the second argument will be the option provided when the rule is used, ex: `{:custom_rule, options}`\n\n## Available Rules\n\n### active_url\n\nThe field under validation must have a valid AAAA or A record. This check is done using `:inet.gethostbyname`\n\n### alpha_dash\n\nThe field under validation may have alpha-numeric characters, as well as dashes and underscores\n\n### boolean\n\nThe field under validation must be able to be cast as a boolean. Accepted input are `true`, `false`, `1`, `0`, `\"1\"`, and `\"0\"`.\n\n### confirmed\n\nThe field under validation must have a matching field of `{field}_confirmation`. For example, if the field under validation is `password`, a matching `password_confirmation` field must be present in the input.\n\n### email\n\nThe field under validation must be a valid email. This rule utilizes the `email_checker` library. By default it checks for email format and mx record to make sure the email does exists.\n\n### {exists, callback}\n\nThe field under validation must exist in a datastore. See sample usage below:\n\n```elixir\nreference: [:required, :alpha_dash, min(2), max(20), {:exists, \u0026reference_exists?/2}]\n# ...\ndefp reference_exists?(tx_no, _opts) do\n  Payments.get_by_reference(tx_no))\nend\n```\n\n### file\n\nThe field under validation must be a successfully uploaded file by checking if the field is a `Plug.Upload` struct.\n\n### {gt, field}\n\nThe field under validation must be greater than the given field. The two fields must be of the same type. Strings, numerics and lists are evaluated using the same conventions as the [size](#size) rule.\n\n```elixir\ninvestment: [:required, :numeric],\nreturn_on_investment: [:required, {:gt, :investment}]\n```\n\n### {in_list, options}\n\nThe field under validation must be included in the given list of values.\n\n```elixir\nregion: [:required, {:in_list, ~w[us-east-2 us-east-1 us-west-1 us-west-2]}]\n```\n\n### list\n\nThe field under validation must be an Elixir `list`.\n\n### {lt, field}\n\nThe field under validation must be less than the given field. The two fields must be of the same type. Strings, numerics and lists are evaluated using the same conventions as the [size](#size) rule.\n\n```elixir\nestimated_churn_rate: [:required, :numeric],\nchurn_rate: [:required, {:lt, :estimated_churn_rate}]\n```\n\n### map\n\nThe field under validation must be an Elixir `map`.\n\n### {max, value}\n\nThe field under validation must be less than or equal to a maximum value. Strings, numerics and lists are evaluated in the same fashion as the [size](#size) rule.\n\n### {min, value}\n\nThe field under validation must have a minimum value. Strings, numerics and lists are evaluated in the same fashion as the [size](#size) rule.\n\n### numeric\n\nThe field under validation must be numeric based on `is_number`.\n\n### required\n\nThe field under validation must be present in the input data and not empty. A field is considered \"empty\" if one of the following conditions are true:\n\n* The value is null.\n* The value is an empty string.\n* The value is an empty list or map.\n\n### {size, value}\n\nThe field under validation must have a size matching the given _value_.\n\n```elixir\n# Validate that a string is exactly 12 characters long...\ntitle: [{:size, 12}]\n\n# Validate that a provided integer equals 10...\nseats: [:numeric, {:size, 10}]\n\n# Validate that an list has exactly 5 elements...\ntags: [:list, {:size, 5}]\n```\n\n### string\n\nThe field under validation must be a string.\n\n### {unique, callback}\n\nThe field under validation must not exist within the a datastore. See sample usage below:\n\n```elixir\nusername: [:required, :alpha_dash, min(2), max(20), {:unique, \u0026is_unique_username?/2}]\n# ...\ndefp is_unique_username?(username, _opts) do\n  is_nil(Accounts.get_user_by_username(username))\nend\n```\n\n### url\n\nThe field under validation must be a valid URL.\n\n## Helper Functions\n\nSee [content](https://hexdocs.pm/request_validator/Request.Validator.Helper.html#content).\n\n## Validating Nested Input\n\nValidating nested map input shouldn't be a problem. For example the HTTP request contains `address` field which is a map with nested attributes (`line1`, `line2`...), you may validate it like so:\n\n```elixir\naddress: map(\n  line1: [:required, :string, {:max, 100}],\n  city: [:required, :string, {:max, 50}],\n  zip_code: [:required, :string, {:max, 10}],\n  country: [:required, :string, {:max, 60}],\n  line2: [:string, {:max, 100}],\n  state: [:string, {:max, 50}]\n)\n```\n\n## TODOS\n\n- [ ] Include more validation rules\n- [ ] Norm validation support\n- [x] Ecto schema support\n\n## License\n\nRequestValidator is released under the MIT License - see the [LICENSE](LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheywhy%2Felixir-request-validator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheywhy%2Felixir-request-validator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheywhy%2Felixir-request-validator/lists"}