{"id":13509746,"url":"https://github.com/CargoSense/vex","last_synced_at":"2025-03-30T14:31:42.106Z","repository":{"id":20842200,"uuid":"24128437","full_name":"CargoSense/vex","owner":"CargoSense","description":"Data Validation for Elixir","archived":false,"fork":false,"pushed_at":"2024-01-29T10:05:32.000Z","size":344,"stargazers_count":587,"open_issues_count":17,"forks_count":60,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-04-14T13:19:23.739Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/CargoSense.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2014-09-17T03:30:24.000Z","updated_at":"2024-04-09T21:08:48.000Z","dependencies_parsed_at":"2024-01-05T21:59:59.329Z","dependency_job_id":"df949d19-2429-4450-8e65-4705c2dd1e2e","html_url":"https://github.com/CargoSense/vex","commit_stats":{"total_commits":136,"total_committers":30,"mean_commits":4.533333333333333,"dds":0.5294117647058824,"last_synced_commit":"715c644c968c4c3d7c07222e18672957d30f14ec"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CargoSense%2Fvex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CargoSense%2Fvex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CargoSense%2Fvex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CargoSense%2Fvex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CargoSense","download_url":"https://codeload.github.com/CargoSense/vex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245550604,"owners_count":20633874,"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":[],"created_at":"2024-08-01T02:01:12.259Z","updated_at":"2025-03-30T14:31:41.608Z","avatar_url":"https://github.com/CargoSense.png","language":"Elixir","funding_links":[],"categories":["Validations","Elixir"],"sub_categories":[],"readme":"# Vex\n\n[![Build Status](https://travis-ci.org/CargoSense/vex.svg)](https://travis-ci.org/CargoSense/vex)\n\nAn extensible data validation library for Elixir.\n\nCan be used to check different data types for compliance with criteria.\n\nShips with built-in validators to check for attribute presence, absence,\ninclusion, exclusion, format, length, acceptance, and by a custom function.\nYou can easily define new validators and override existing ones.\n\nInspired by\n-----------\n\n * Rails ActiveModel Validations\n * Clojure's [Validateur](https://github.com/michaelklishin/validateur)\n\nSupported Validations\n---------------------\n\nNote the examples below use `Vex.valid?/2`, with the validations to\ncheck explicitly provided as the second argument. For information on how\nvalidation configuration can be provided as part of a single argument\n(eg, packaged with the data to check passed to `Vex.valid?/1`) see\n\"Configuring Validations\" below.\n\nNote all validations can be skipped based on `:if` and `:unless`\nconditions given as options. See \"Validation Conditions\" further below for\nmore information.\n\n### Presence\n\nEnsure a value is present:\n\n```elixir\nVex.valid? post, title: [presence: true]\n```\n\nSee the documentation on `Vex.Validators.Presence` for details on\navailable options.\n\n### Absence\n\nEnsure a value is absent (blank)\n\n```elixir\nVex.valid? post, byline: [absence: true]\n```\n\nSee the documentation on `Vex.Validators.Absence` for details on\navailable options.\n\n### Inclusion\n\nEnsure a value is in a list of values:\n\n```elixir\nVex.valid? post, category: [inclusion: [\"politics\", \"food\"]]\n```\n\nThis validation can be skipped for `nil` or blank values by including\n`allow_nil: true` and/or `allow_blank: true`.\n\nSee the documentation on `Vex.Validators.Inclusion` for details on available options.\n\n### Exclusion\n\nEnsure a value is _not_ in a list of values:\n\n```elixir\nVex.valid? post, category: [exclusion: [\"oped\", \"lifestyle\"]]\n```\n\nSee the documentation on `Vex.Validators.Exclusion` for details on available\noptions.\n\n### Format\n\nEnsure a value matches a regular expression:\n\n```elixir\nVex.valid? widget, identifier: [format: ~r/(^id-)/]\n```\n\nThis validation can be skipped for `nil` or blank values by including\n`allow_nil: true` and/or `allow_blank: true`.\n\nSee the documentation on `Vex.Validators.Format` for details on\navailable options.\n\n### Length\n\nEnsure a value's length is at least a given size:\n\n```elixir\nVex.valid? user, username: [length: [min: 2]]\n```\n\nEnsure a value's length is at or below a given size:\n\n```elixir\nVex.valid? user, username: [length: [max: 10]]\n```\n\nEnsure a value's length is within a range (inclusive):\n\n```elixir\nVex.valid? user, username: [length: [in: 2..10]]\n```\n\nThis validation can be skipped for `nil` or blank values by including\n`allow_nil: true` and/or `allow_blank: true`.\n\nSee the documentation on `Vex.Validators.Length` for details on\navailable options.\n\n### Number\n\nEnsure a value is a number greater than a given number:\n\n```elixir\nVex.valid? value, number: [greater_than: 2]\n```\n\nEnsure a value is a number less than or equal to a given number:\n\n```elixir\nVex.valid? value, number: [less_than_or_equal_to: 10]\n```\n\nEnsure a value is a number is within a given range:\n\n```elixir\nVex.valid? value, number: [greater_than_or_equal_to: 0, less_than: 10]\n```\n\nThis validation can be skipped for `nil` or blank values by including \n`allow_nil: true` or `allow_blank: true` respectively in the options.\n\nSee the documentation on `Vex.Validators.Number` for details\non available options.\n\n### UUID\n\nEnsure a value is a valid UUID string:\n\n```elixir\nVex.valid? value, uuid: true\n```\n\nEnsure a value is a valid UUID string in a given format:\n\n```elixir\nVex.valid? value, uuid: [format: :hex]\n```\n\nThis validation can be skipped for `nil` or blank values by including \n`allow_nil: true` or `allow_blank: true` respectively in the options.\n\nSee the documentation on `Vex.Validators.Uuid` for details\non available options.\n\n### Acceptance\n\nEnsure an attribute is set to a positive (or custom) value. For use\nexpecially with \"acceptance of terms\" checkboxes in web applications.\n\n```elixir\nVex.valid?(user, accepts_terms: [acceptance: true])\n```\n\nTo check for a specific value, use `:as`:\n\n```elixir\nVex.valid?(user, accepts_terms: [acceptance: [as: \"yes\"]])\n```\n\nSee the documentation on `Vex.Validators.Acceptance` for details on\navailable options.\n\n### Confirmation\n\nEnsure a value has a matching confirmation:\n\n```elixir\nVex.valid? user, password: [confirmation: true]\n```\n\nThe above would ensure the values of `password` and\n`password_confirmation` are equivalent.\n\nThis validation can be skipped for `nil` or blank values by\nincluding `allow_nil: true` and/or `allow_blank: true`.\n\nSee the documentation on `Vex.Validators.confirmation` for details\non available options.\n\n### Custom Function\n\nYou can also just provide a custom function for validation instead of\na validator name:\n\n```elixir\nVex.valid?(user, password: fn (pass) -\u003e byte_size(pass) \u003e 4 end)\nVex.valid? user, password: \u0026valid_password?/1\nVex.valid?(user, password: \u0026(\u00261 != \"god\"))\n```\n\nInstead of returning a boolean the validate function may return `:ok`\non success, or `{:error, \"a message\"}` on error:\n\n```elixir\nVex.valid?(user, password: fn (password) -\u003e\n  if valid_password?(password) do\n    :ok\n  else\n    {:error, \"#{password} isn't a valid password\"}\n  end\nend)\n```\n\nOr explicitly using `:by`:\n\n```elixir\nVex.valid?(user, age: [by: \u0026(\u00261 \u003e 18)])\n```\n\nThis validation can be skipped for `nil` or blank values by including\n`allow_nil: true` and/or `allow_blank: true`.\n\nSee the documentation on `Vex.Validators.By` for details on available options.\n\nValidation Conditions\n---------------------\n\nA validation can be made applicable (or unapplicable) by using the `:if`,\n`:if_any`, `:unless` and `:unless_any` options.\n\nNote `Vex.results` will return tuples with `:not_applicable` for validations that\nare skipped as a result of failing conditions.\n\n### Based on another attribute's presence\n\nRequire a post to have a `body` of at least 200 bytes unless a non-blank\n`reference_url` is provided.\n\n```elixir\niex\u003e Vex.valid?(post, body: [length: [min: 200, unless: :reference_url]])\n```\n\n### Based on other attributes' presence\n\nRequire a post to have a `body` of at least 200 bytes unless a non-blank\n`reference_url`__and__ `category` are provided.\n\n```elixir\niex\u003e Vex.valid?(post, body: [length: [min: 200, unless: [:reference_url, :category]]])\n```\n\nRequire a post to have a `body` of at least 200 bytes unless a non-blank\n`reference_url` __or__ `category` is provided.\n\n```elixir\niex\u003e Vex.valid?(post, body: [length: [min: 200, unless_any: [:reference_url, :category]]])\n```\n\n### Based on another attribute's value\n\nOnly require a password if the `state` of a user is `:new`:\n\n```elixir\niex\u003e Vex.valid?(user, password: [presence: [if: [state: :new]]]\n```\n\n### Based on other attributes' value\n\nOnly require a password if the `state` of a user is `:new` __and__ she is not from Facebook:\n\n```elixir\niex\u003e Vex.valid?(user, password: [presence: [if: [state: :new, from_facebook: false]]]\n```\n\nOnly require a password if the `state` of a user is `:new` __or__ she is not from Facebook:\n\n```elixir\niex\u003e Vex.valid?(user, password: [presence: [if_any: [state: :new, from_facebook: false]]]\n```\n\n### Based on a custom function\n\nDon't require users from Facebook to provide an email address:\n\n```elixir\niex\u003e Vex.valid?(user, email: [presence: [unless: \u0026User.from_facebook?/1]]\n```\n\nRequire users less than 13 years of age to provide a parent's email address:\n\n```elixir\niex\u003e Vex.valid?(user, parent_email: [presence: [if: \u0026(\u00261.age \u003c 13)]]\n```\n\nConfiguring Validations\n-----------------------\n\nThe examples above use `Vex.valid?/2`, passing both the data to\nbe validated and the validation settings. This is nice for ad hoc data\nvalidation, but wouldn't it be nice to just:\n\n```elixir\nVex.valid?(data)\n```\n\n... and have the data tell Vex which validations should be evaluated?\n\n### In Structs\n\nIn your struct module, use `Vex.Struct`:\n\n```elixir\ndefmodule User do\n  defstruct username: nil, password: nil, password_confirmation: nil\n  use Vex.Struct\n\n  validates :username, presence: true,\n                       length: [min: 4],\n                       format: ~r/^[[:alpha:]][[:alnum:]]+$/\n  validates :password, length: [min: 4],\n                       confirmation: true\nend\n```\n\nNote `validates` should only be used once per attribute.\n\nOnce configured, you can use `Vex.valid?/1`:\n\n```elixir\nuser = %User{username: \"actualuser\",\n             password: \"abcdefghi\",\n             password_confirmation: \"abcdefghi\"}\n\nVex.valid?(user)\n```\n\nYou can also use `valid?` directly from the Module:\n\n```elixir\nuser |\u003e User.valid?\n```\n\n### In Keyword Lists\n\nIn your list, just include a `:_vex` entry and use `Vex.valid?/1`:\n\n```elixir\nuser = [username: \"actualuser\",\n        password: \"abcdefghi\",\n        password_confirmation: \"abcdefghi\",\n        _vex: [username: [presence: true,\n                          length: [min: 4],\n                          format: ~r/^[[:alpha:]][[:alnum:]]+$/]],\n               password: [length: [min: 4], confirmation: true]]\nVex.valid?(user)\n```\n\n### Others\n\nJust implement the `Vex.Extract` protocol. Here's what was done to\nsupport keyword lists:\n\n```elixir\ndefimpl Vex.Extract, for: List do\n  def settings(data) do\n    Keyword.get data, :_vex\n  end\n  def attribute(data, name) do\n    Keyword.get data, name\n  end\nend\n```\n\nQuerying Results\n----------------\n\nFor validity, it's the old standard, `Vex.valid?/1`:\n\n```elixir\niex\u003e Vex.valid?(user)\ntrue\n```\n\n(If you need to pass in the validations to use, do that as a second argument to\n`Vex.valid?/2`)\n\nYou can access the raw validation results using `Vex.results/1`:\n\n```elixir\niex\u003e Vex.results(user)\n[{:ok, :username, :presence},\n {:ok, :username, :length},\n {:ok, :username, :format}]\n```\n\nIf you only want the errors, use `Vex.errors/1`:\n\n```elixir\niex\u003e Vex.errors(another_user)\n[{:error, :password, :length, \"must have a length of at least 4\"},\n {:error, :password, :confirmation, \"must match its confirmation\"}]\n ```\n\nError Message Renderers\n-----------------------\n\nBy default Vex uses `Vex.ErrorRenderers.EEx` as default renderer, also have\n`Vex.ErrorRenderers.Parameterized`, and you have ability to define your own.\n\nFor example if we want to use [Linguist](https://github.com/chrismccord/linguist)\nfor internationalization, we can do the following:\n\n\n```elixir\n  defmodule I18nErrorRenderer do\n    @behaviour Vex.ErrorRenderer\n    use Linguist.Vocabulary\n\n    locale \"en\", [\n      foo: [\n        too_short: \"too short, min %{min} chars\",\n        must_start_with_f: \"must start with an f\",\n      ],\n    ]\n\n    locale \"kr\", [\n      foo: [\n        too_short: \"너무 짧으면, 최소 %{min} 개 문자\",\n        must_start_with_f: \"f로 시작해야합니다\",\n      ],\n    ]\n\n    def message(options, _default, context \\\\ []) do\n      message = options[:message] || raise \"message is needed for proper i18n\"\n      locale = options[:locale] || \"en\"\n      t!(locale, message, context)\n    end\n  end\n\n  result = Vex.validate([name: \"Foo\"], name: [\n    length: [\n      min: 4,\n      error_renderer: I18nErrorRenderer,\n      message: \"foo.too_short\"\n    ],\n    format: [\n      with: ~r/^f/,\n      locale: \"kr\",\n      error_renderer: I18nErrorRenderer,\n      message: \"foo.must_start_with_f\",\n    ]\n  ])\n  assert {:error, [\n    {:error, :name, :length, \"too short, min 4 chars\"},\n    {:error, :name, :format, \"f로 시작해야합니다\"}\n  ]} = result\n```\n\nWe can set error renderer globally:\n\n```elixir\nconfig :vex,\n  error_renderer: Vex.ErrorRenderers.Parameterized\n```\n\nValidators declare a list of the available message fields and their\ndescriptions by setting the module attribute `@message_fields` (see\n`Vex.Validator.ErrorMessage`), and the metadata is available for querying:\n\n```elixir\niex\u003e Vex.Validators.Length.__validator__(:message_fields)\n[value: \"Bad value\", tokens: \"Tokens from value\", size: \"Number of tokens\",\n min: \"Minimum acceptable value\", max: \"Maximum acceptable value\"]\n```\n\nCustom EEx Error Renderer Messages\n---------------------\n\nCustom error messages can be requested by validations when providing the\n`:message` option and can use EEx to insert fields specific to the validator, eg:\n\n```elixir\nvalidates :body, length: [min: 4,\n                          tokenizer: \u0026String.split/1,\n                          message: \"\u003c%= length tokens %\u003e words isn't enough\"]\n```\n\nThis could yield, in the case of a `:body` value `\"hello my darling\"`, the result:\n\n```elixir\n{:error, \"3 words isn't enough\"}\n```\n\nAdding and Overriding Validators\n--------------------------------\n\nValidators are simply modules that implement `validate/2` and return `:ok`\nor a tuple with `:error` and a message. They usually use `Vex.Validator`\nas well to get some common utilities for supporting `:allow_nil`, `:allow_blank`, and custom `:message` options:\n\n```elixir\ndefmodule App.CurrencyValidator do\n\n  use Vex.Validator\n\n  def validate(value, options) do\n    # Return :ok or {:error, \"a message\"}\n  end\n\nend\n```\n\nIf you wanted to make this validator available to Vex as the `:currency`\nvalidator so that you could do this:\n\n```elixir\nvalidates :amount, currency: true\n```\n\nYou just need to add a validator _source_ so that Vex knows where to find it.\n\nA source can be anything that implements the `Vex.Validator.Source` protocol.\nWe'll use a keyword list for this example. The implementation for `List`\nallows us to provide a simple mapping.\n\nVex uses `Application.get_env(:vex, :sources)` to retrieve the\nconfiguration of sources, defaulting to `[Vex.Validators]`. We can\nset the configuration with\n[Mix.Config](http://elixir-lang.org/docs/stable/mix/Mix.Config.html),\nas in:\n\n```elixir\nconfig :vex,\n  sources: [[currency: App.CurrencyValidator], Vex.Validators]\n```\n\nVex will consult the list of sources -- in order -- when looking for a\nvalidator. By putting our new source before `Vex.Validators`, we make it\npossible to override the built-in validators.\n\nNote: Without a `sources` configuration, Vex falls back to a default of `[Vex.Validators]`.\n\n### Using Modules as Sources\n\nIf adding mappings to our keyword list source becomes\ntiresome, we can make use of the fact there's a `Vex.Validator.Source`\nimplementation for `Atom`; we can provide a module name as a source instead\n(just as Vex does with `Vex.Validators`).\n\n```elixir\nconfig :vex,\n  sources: [App.Validators, Vex.Validators]\n```\n\nIf given an atom, Vex will assume it refers to a module and try two\nstrategies to retrieve a validator:\n\n * If the module exports a `validator/1` function, it will call that\n   function, passing the validator name (eg, `:currency`)\n * Otherwise, Vex will assume the validator module is the same as the\n   source module _plus_ a dot and the camelized validator name (eg, given\n   a source of `App.Validators`, it would look for a `:currency` validator at\n   `App.Validators.Currency`)\n\nIn either case it will check the candidate validator for an exported\n`validate/2` function.\n\nIn the event no validators can be found for a name, a\n`Vex.InvalidValidatorError` will be raised.\n\n### Checking Validator Lookup\n\nTo see what validator Vex finds for a given validator name, use `Vex.validator/1`:\n\n```elixir\niex\u003e Vex.validator(:currency)\nApp.Validators.Currency\n```\n\nContributing\n------------\n\nPlease fork and send pull requests (preferably from non-master branches), including tests (doctests or normal `ExUnit.Case` tests).\n\nReport bugs and request features via [Issues](https://github.com/CargoSense/vex/issues);\nkudos if you do it from pull requests you submit that fix the bugs or add\nthe features. ;)\n\nLicense\n-------\n\nReleased under the [MIT License](http://www.opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCargoSense%2Fvex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCargoSense%2Fvex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCargoSense%2Fvex/lists"}