Giter VIP home page Giter VIP logo

vex's Introduction

Vex

Build Status

An extensible data validation library for Elixir.

Can be used to check different data types for compliance with criteria.

Ships with built-in validators to check for attribute presence, absence, inclusion, exclusion, format, length, acceptance, and by a custom function. You can easily define new validators and override existing ones.

Inspired by

  • Rails ActiveModel Validations
  • Clojure's Validateur

Supported Validations

Note the examples below use Vex.valid?/2, with the validations to check explicitly provided as the second argument. For information on how validation configuration can be provided as part of a single argument (eg, packaged with the data to check passed to Vex.valid?/1) see "Configuring Validations" below.

Note all validations can be skipped based on :if and :unless conditions given as options. See "Validation Conditions" further below for more information.

Presence

Ensure a value is present:

Vex.valid? post, title: [presence: true]

See the documentation on Vex.Validators.Presence for details on available options.

Absence

Ensure a value is absent (blank)

Vex.valid? post, byline: [absence: true]

See the documentation on Vex.Validators.Absence for details on available options.

Inclusion

Ensure a value is in a list of values:

Vex.valid? post, category: [inclusion: ["politics", "food"]]

This validation can be skipped for nil or blank values by including allow_nil: true and/or allow_blank: true.

See the documentation on Vex.Validators.Inclusion for details on available options.

Exclusion

Ensure a value is not in a list of values:

Vex.valid? post, category: [exclusion: ["oped", "lifestyle"]]

See the documentation on Vex.Validators.Exclusion for details on available options.

Format

Ensure a value matches a regular expression:

Vex.valid? widget, identifier: [format: ~r/(^id-)/]

This validation can be skipped for nil or blank values by including allow_nil: true and/or allow_blank: true.

See the documentation on Vex.Validators.Format for details on available options.

Length

Ensure a value's length is at least a given size:

Vex.valid? user, username: [length: [min: 2]]

Ensure a value's length is at or below a given size:

Vex.valid? user, username: [length: [max: 10]]

Ensure a value's length is within a range (inclusive):

Vex.valid? user, username: [length: [in: 2..10]]

This validation can be skipped for nil or blank values by including allow_nil: true and/or allow_blank: true.

See the documentation on Vex.Validators.Length for details on available options.

Acceptance

Ensure an attribute is set to a positive (or custom) value. For use expecially with "acceptance of terms" checkboxes in web applications.

Vex.valid?(user, accepts_terms: [acceptance: true])

To check for a specific value, use :as:

Vex.valid?(user, accepts_terms: [acceptance: [as: "yes"]])

See the documentation on Vex.Validators.Acceptance for details on available options.

Confirmation

Ensure a value has a matching confirmation:

Vex.valid? user, password: [confirmation: true]

The above would ensure the values of password and password_confirmation are equivalent.

This validation can be skipped for nil or blank values by including allow_nil: true and/or allow_blank: true.

See the documentation on Vex.Validators.confirmation for details on available options.

Custom Function

You can also just provide a custom function for validation instead of a validator name:

Vex.valid?(user, password: fn (pass) -> byte_size(pass) > 4 end)
Vex.valid? user, password: &valid_password?/1
Vex.valid?(user, password: &(&1 != "god"))

Instead of returning a boolean the validate function may return :ok on success, or {:error, "a message"} on error:

Vex.valid?(user, password: fn (password) ->
  if valid_password?(p) do
    :ok
  else
    {:error, "#{password} isn't a valid password"}
  end
end)

Or explicitly using :by:

Vex.valid?(user, age: [by: &(&1 > 18)])

This validation can be skipped for nil or blank values by including allow_nil: true and/or allow_blank: true.

See the documentation on Vex.Validators.By for details on available options.

Validation Conditions

A validation can be made applicable (or unapplicable) by using the :if and :unless options.

Note Vex.results will return tuples with :not_applicable for validations that are skipped as a result of failing conditions.

Based on another attribute's presence

Require a post to have a body of at least 200 bytes unless a non-blank reference_url is provided.

iex> Vex.valid?(post, body: [length: [min: 200, unless: :reference_url]])

Based on another attribute's value

Only require a password if the state of a user is :new:

iex> Vex.valid?(user, password: [presence: [if: [state: :new]]]

Based on a custom function

Don't require users from Facebook to provide an email address:

iex> Vex.valid?(user, email: [presence: [unless: &User.from_facebook?/1]]

Require users less than 13 years of age to provide a parent's email address:

iex> Vex.valid?(user, parent_email: [presence: [if: &(&1.age < 13)]]

Configuring Validations

The examples above use Vex.valid?/2, passing both the data to be validated and the validation settings. This is nice for ad hoc data validation, but wouldn't it be nice to just:

Vex.valid?(data)

... and have the data tell Vex which validations should be evaluated?

In Structs

In your struct module, use Vex.Struct:

defmodule User do
  defstruct username: nil, password: nil, password_confirmation: nil
  use Vex.Struct

  validates :username, presence: true,
                       length: [min: 4],
                       format: ~r/^[[:alpha:]][[:alnum:]]+$/
  validates :password, length: [min: 4],
                       confirmation: true
end

Note validates should only be used once per attribute.

Once configured, you can use Vex.valid?/1:

user = %User{username: "actualuser",
             password: "abcdefghi",
             password_confirmation: "abcdefghi"}

Vex.valid?(user)

You can also use valid? directly from the Module:

user |> User.valid?

In Keyword Lists

In your list, just include a :_vex entry and use Vex.valid?/1:

user = [username: "actualuser",
        password: "abcdefghi",
        password_confirmation: "abcdefghi",
        _vex: [username: [presence: true,
                          length: [min: 4],
                          format: ~r/^[[:alpha:]][[:alnum:]]+$/]],
               password: [length: [min: 4], confirmation: true]]
Vex.valid?(user)

Others

Just implement the Vex.Extract protocol. Here's what was done to support keyword lists:

defimpl Vex.Extract, for: List do
  def settings(data) do
    Keyword.get data, :_vex
  end
  def attribute(data, name) do
    Keyword.get data, name
  end
end

Querying Results

For validity, it's the old standard, Vex.valid?/1:

iex> Vex.valid?(user)
true

(If you need to pass in the validations to use, do that as a second argument to Vex.valid?/2)

You can access the raw validation results using Vex.results/1:

iex> Vex.results(user)
[{:ok, :username, :presence},
 {:ok, :username, :length},
 {:ok, :username, :format}]

If you only want the errors, use Vex.errors/1:

iex> Vex.errors(another_user)
[{:error, :password, :length, "must have a length of at least 4"},
 {:error, :password, :confirmation, "must match its confirmation"}]

Custom Error Messages

Custom error messages can be requested by validations when providing the :message option and can use EEx to insert fields specific to the validator, eg:

validates :body, length: [min: 4,
                          tokenizer: &String.split/1,
                          message: "<%= length tokens %> words isn't enough"]

This could yield, in the case of a :body value "hello my darling", the result:

{:error, "3 words isn't enough"}

Validators declare a list of the available message fields and their descriptions by setting the module attribute @message_fields (see Vex.Validator.ErrorMessage), and the metadata is available for querying:

iex> Vex.Validators.Length.__validator__(:message_fields)
[value: "Bad value", tokens: "Tokens from value", size: "Number of tokens",
 min: "Minimum acceptable value", max: "Maximum acceptable value"]

Adding and Overriding Validators

Validators are simply modules that implement validate/2 and return :ok or a tuple with :error and a message. They usually use Vex.Validator as well to get some common utilities for supporting :allow_nil, :allow_blank, and custom :message options:

defmodule App.CurrencyValidator do

  use Vex.Validator

  def validate(value, options) do
    # Return :ok or {:error, "a message"}
  end

end

If you wanted to make this validator available to Vex as the :currency validator so that you could do this:

validates :amount, currency: true

You just need to add a validator source so that Vex knows where to find it.

A source can be anything that implements the Vex.Validator.Source protocol. We'll use a keyword list for this example. The implementation for List allows us to provide a simple mapping.

Vex uses Application.get_env(:vex, :sources) to retrieve the configuration of sources, defaulting to [Vex.Validators]. We can set the configuration with Mix.Config, as in:

config :vex,
  sources: [[currency: App.CurrencyValidator], Vex.Validators]

Vex will consult the list of sources -- in order -- when looking for a validator. By putting our new source before Vex.Validators, we make it possible to override the built-in validators.

Note: Without a sources configuration, Vex falls back to a default of [Vex.Validators].

Using Modules as Sources

If adding mappings to our keyword list source becomes tiresome, we can make use of the fact there's a Vex.Validator.Source implementation for Atom; we can provide a module name as a source instead (just as Vex does with Vex.Validators).

config :vex,
  sources: [App.Validators, Vex.Validators]

If given an atom, Vex will assume it refers to a module and try two strategies to retrieve a validator:

  • If the module exports a validator/1 function, it will call that function, passing the validator name (eg, :currency)
  • Otherwise, Vex will assume the validator module is the same as the source module plus a dot and the camelized validator name (eg, given a source of App.Validators, it would look for a :currency validator at App.Validators.Currency)

In either case it will check the candidate validator for an exported validate/2 function.

In the event no validators can be found for a name, a Vex.InvalidValidatorError will be raised.

Checking Validator Lookup

To see what validator Vex finds for a given validator name, use Vex.validator/1:

iex> Vex.validator(:currency)
App.Validators.Currency

Contributing

Please fork and send pull requests (preferably from non-master branches), including tests (doctests or normal ExUnit.Case tests).

Report bugs and request features via Issues; kudos if you do it from pull requests you submit that fix the bugs or add the features. ;)

License

Released under the MIT License.

vex's People

Contributors

bruce avatar benwilson512 avatar ismaelga avatar jtmoulia avatar eraserewind avatar mindreframer avatar chrismccord avatar lowks avatar

Watchers

Jason Harrelson avatar James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.