Vaxin (Vaxin v0.3.0) View Source

Contains the core functionality to work with Vaxin.

Vaxin at its core is a data validator combinator library. It tries to solve the problem of validating the shape and content of some data (most useful when such data come from an external source) and of conforming those data to arbitrary formats.

Vaxin is based on the concept of validators: a validator is something that knows how to validate a term and transform it to something else if necessary. A good example of a validator could be something that validates that a term is a string representation of an integer and that converts such string to the represented integer.

Validators

A validator is a function that takes one argument and returns either:

  • {:ok, transformed} - indicating the validation has succeeded (the input term is considered valid) and transformed is the conformed value for the input term.

  • {:error, reason} - indicating means the validation has failed (the input term is invalid). reason can be a string representing the error message or a Vaxin.Error. Note that validate/2 will eventually wrap the error message into a Vaxin.Error.

  • true - indicating the validation has succeeded. It has the same effect as {:ok, transformed}, but it can be used when the transformed value is the same as the input value. This is useful for "predicate" validators (functions that take one argument and return a boolean).

  • false - it means validation failed. It is the same as {:error, reason}, except the reason only mentions that a "predicate failed".

Returning a boolean value is supported so that existing predicate functions can be used as validators without modification. Examples of such functions are type guards (is_binary/1 or is_list/1), functions like String.valid?/1, and many others.

The concept of validators is very powerful as they can be easily combined: for example, the Vaxin.all_of/1 function takes a list of validators and returns a validator that passes if all of the given validators pass. Vaxin provides both "basic" validators as well as validator combinators.

Built-in validators

On top of powerful built-in Elixir predicate functions, Vaxin also provides a few built-in validators. You might notice that they are very similar to the Ecto.Changeset API. The intention is to enable developers who are familiar with Ecto to be immediately productive with Vaxin. However, there is a few fundamental difference between two libraries:

  • Vaxin built-in validators take in options and return a validator which can be used with Vaxin.validate/2 later.

  • Vaxin does not have the concept of "empty" values. nil or empty strings are treated the same way as other Elixir data.

Consider the following example: nil will be validated with Vaxin while Ecto would skip it.

iex> import Vaxin
iex> validator = validate_number(greater_than: 0)
iex> {:error, error} = validate(validator, nil)
iex> Exception.message(error)
"must be a number"

Examples

Let's say S.H.I.E.L.D are looking for a replacement for Captain America and receive thousands of applications, they could use Vaxin to build a profile validator.

iex> import Vaxin
iex>
iex> age_validator =
...>   validate_number(
...>     &is_integer/1,
...>     greater_than: 18,
...>     message: "is too young to be a superhero"
...>   )
iex>
iex> superpower_validator =
...>   validate_inclusion(
...>     &is_binary/1,
...>     ["fly", "strength", "i-can-do-this-all-day"],
...>     message: "is unfortunately not the super-power we are looking for"
...>   )
iex> superhero_validator =
...>   (&is_map/1)
...>   |> validate_key("age", :required, age_validator)
...>   |> validate_key("superpower", :required, superpower_validator)
iex>
iex> peter_parker = %{"age" => 16, "superpower" => "speed"}
iex> {:error, error} = Vaxin.validate(superhero_validator, peter_parker)
iex> Exception.message(error)
~s("age" is too young to be a superhero)
iex>
iex> falcon = %{"age" => 40, "superpower" => "fly"}
iex> Vaxin.validate(superhero_validator, falcon)
{:ok, %{"age" => 40, "superpower" => "fly"}}

Link to this section Summary

Functions

Returns a validator that passes when all the given validators pass.

Combines validator1 with validator2. Note that validator2 will only be executed if validator1 succeeds.

Merges a validator returned by an anonymous function.

Returns a validator that always passes. It is useful placing in the beginning of the validator chain.

Puts the given value under key unless the entry key already exists in map

Returns a validator that always passes and applies the given transformer.

Validates value against validator.

Combine combinator with a validator that validates every item in an enum against each_validator.

Combines combinator with a validator that validates the term is excluded in permitted.

Combines combinator with a validator that validates the term matches the given regular expression.

Combines combinator with a validator that validates the term is included in permitted.

Combines combinator with a validator that checks the value of key in a map.

Combines combinator with a validator that validates the term as a number.

Combines combinator with a validator that validates string length.

Link to this section Types

Specs

validator() ::
  (any() ->
     {:ok, any()} | {:error, String.t()} | {:error, Vaxin.Error.t()} | boolean())

Link to this section Functions

Specs

all_of([validator(), ...]) :: validator()

Returns a validator that passes when all the given validators pass.

Examples

iex> validator = Vaxin.all_of([&is_integer/1, &(&1 >= 1)])
iex> Vaxin.validate(validator, 1)
{:ok, 1}
iex> {:error, %Vaxin.Error{message: "is invalid"}} = Vaxin.validate(validator, 0)
Link to this function

combine(validator1, validator2)

View Source

Specs

combine(validator(), validator()) :: validator()

Combines validator1 with validator2. Note that validator2 will only be executed if validator1 succeeds.

Examples

iex> validator = Vaxin.combine(&is_integer/1, &(&1 >= 1))
iex> Vaxin.validate(validator, 1)
{:ok, 1}
iex> {:error, %Vaxin.Error{message: "is invalid"}} = Vaxin.validate(validator, 0)

Specs

merge(validator(), (any() -> validator())) :: validator()

Merges a validator returned by an anonymous function.

This function is useful when the validator to be combined requires information from the previous validator. If you want to simply merge two validators, use Vaxin.combine/2 instead.

Examples

iex> validator =
...>   validate_key("type", :required, validate_inclusion(["user", "guest"]))
...>   |> merge(fn
...>     %{"type" => "user"} ->
...>       validate_key("user_id", :required, &is_binary/1)
...>
...>     %{"type" => "guest"} ->
...>       validate_key("guest_id", :required, &is_binary/1)
...>   end)
...>
iex> Vaxin.validate(validator, %{"type" => "user", "user_id" => "user-1"})
{:ok, %{"type" => "user", "user_id" => "user-1"}}
iex> Vaxin.validate(validator, %{"type" => "guest", "guest_id" => "guest-1"})
{:ok, %{"type" => "guest", "guest_id" => "guest-1"}}

Specs

noop() :: (any() -> {:ok, any()})

Returns a validator that always passes. It is useful placing in the beginning of the validator chain.

Examples

iex> validator = Vaxin.noop() |> Vaxin.validate_inclusion([:foo, "foo"])
iex> Vaxin.validate(validator, :foo)
{:ok, :foo}
iex> Vaxin.validate(validator, "foo")
{:ok, "foo"}
Link to this function

put_new(combinator \\ noop(), key, value)

View Source

Specs

put_new(validator(), key :: any(), value :: any()) :: validator()

Puts the given value under key unless the entry key already exists in map

Examples

iex> import Vaxin
iex> validator = validate_key(&is_map/1, "foo", :optional, &is_binary/1)
iex> validator = put_new(validator, "foo", "bar")
iex> validate(validator, %{})
{:ok, %{"foo" => "bar"}}

iex> validator = validate_key(&is_map/1, "foo", :optional, &is_binary/1)
iex> validator = put_new(validator, "foo", "foo")
iex> validate(validator, %{"foo" => "bar"})
{:ok, %{"foo" => "bar"}}
Link to this function

transform(combinator \\ noop(), transformer)

View Source

Specs

transform(validator(), (any() -> any())) :: validator()

Returns a validator that always passes and applies the given transformer.

Examples

iex> import Vaxin
iex> validator = transform(noop(), &String.to_integer/1)
iex> validate(validator, "1")
{:ok, 1}
Link to this function

validate(validator, value)

View Source

Specs

validate(validator(), any()) :: {:ok, any()} | {:error, Vaxin.Error.t()}

Validates value against validator.

Examples

iex> Vaxin.validate(&is_atom/1, :foo)
{:ok, :foo}
iex> Vaxin.validate(&is_atom/1, "foo")
{:error, %Vaxin.Error{validator: &is_atom/1, message: "must be an atom", metadata: [kind: :is_atom]}}
Link to this function

validate_enum(combinator, each_validator, options \\ [])

View Source

Specs

validate_enum(validator(), validator(), Keyword.t()) :: validator()

Combine combinator with a validator that validates every item in an enum against each_validator.

Options

  • skip_invalid? - (boolean) if true, skips all invalid items. Defaults to false.
  • into - the collectable where the transformed values should end up in. Defaults to [].

Examples

iex> import Vaxin
iex> validator = validate_enum(&is_list/1, &is_integer/1)
iex> Vaxin.validate(validator, [1, 2])
{:ok, [1, 2]}
iex> {:error, error} = Vaxin.validate(validator, [1, "2"])
iex> Exception.message(error)
"[1] must be an integer"
iex> validator = validate_enum(&is_list/1, &is_integer/1, skip_invalid?: true)
iex> Vaxin.validate(validator, [1, "2"])
{:ok, [1]}
Link to this function

validate_exclusion(validator \\ noop(), reversed, options \\ [])

View Source

Specs

validate_exclusion(validator(), Enum.t(), Keyword.t()) :: validator()

Combines combinator with a validator that validates the term is excluded in permitted.

Options

  • message - the error message on failure. Defaults to "is reserved".

Examples

iex> import Vaxin
iex> validator = validate_exclusion(["foo", "bar"])
iex> {:error, error} = validate(validator, "foo")
iex> Exception.message(error)
"is reserved"
Link to this function

validate_format(combinator \\ &String.valid?/1, format, options \\ [])

View Source

Specs

validate_format(validator(), Regex.t(), Keyword.t()) :: validator()

Combines combinator with a validator that validates the term matches the given regular expression.

Options

  • message - the error message when the format validator fails. Defaults to "has invalid format".

Examples

iex> import Vaxin
iex> validator = validate_format(&String.valid?/1, ~r/@/)
iex> validate(validator, "foo@bar.com")
{:ok, "foo@bar.com"}
Link to this function

validate_inclusion(validator \\ noop(), permitted, options \\ [])

View Source

Specs

validate_inclusion(validator(), Enum.t(), Keyword.t()) :: validator()

Combines combinator with a validator that validates the term is included in permitted.

Options

  • message - the error message on failure. Defaults to "is invalid".

Examples

iex> import Vaxin
iex> validator = validate_inclusion(["foo", "bar"])
iex> validate(validator, "foo")
{:ok, "foo"}
Link to this function

validate_key(combinator \\ &is_map/1, key, required_or_optional, value_validator, options \\ [])

View Source

Specs

validate_key(
  validator(),
  any(),
  :required | :optional,
  validator(),
  Keyword.t()
) :: validator()

Combines combinator with a validator that checks the value of key in a map.

Options

  • message - the message on failure. Defaults to "is required" or the error returned by value_validator.

Examples

iex> tinyint_validator = validate_number(greater_than_or_equal_to: -128, less_than: 128, message: "must be a tinyint")
iex> validator = Vaxin.validate_key(:id, :required, tinyint_validator)
iex> Vaxin.validate(validator, %{id: 1})
{:ok, %{id: 1}}
iex> {:error, error} = Vaxin.validate(validator, %{id: 129})
iex> Exception.message(error)
"id must be a tinyint"
Link to this function

validate_number(combinator \\ &is_number/1, options)

View Source

Specs

validate_number(validator(), Keyword.t()) :: validator()

Combines combinator with a validator that validates the term as a number.

Options

  • less_than - the number must be less than this value.
  • greater_than - the number must be greater than this value.
  • less_than_or_equal_to - the number must be less than or equal to this value.
  • greater_than_or_equal_to - the number must be greater than or equal to this value.
  • equal_to - the number must be equal to this value.
  • not_equal_to - the number must be not equal to this value.
  • message - the error message when the number validator fails. Defaults to either:
    • must be less than %{number}
    • must be greater than %{number}
    • must be less than or equal to %{number}
    • must be greater than or equal to %{number}
    • must be equal to %{number}
    • must be not equal to %{number}

Examples

iex> validator = Vaxin.validate_number(greater_than: 1, less_than: 20)
iex> Vaxin.validate(validator, 10)
{:ok, 10}
iex> {:error, error} = Vaxin.validate(validator, 20)
iex> Exception.message(error)
"must be less than 20"
Link to this function

validate_string_length(validator \\ &String.valid?/1, options)

View Source

Specs

validate_string_length(validator(), Keyword.t()) :: validator()

Combines combinator with a validator that validates string length.

Options

  • exact - the length must be exact this value.
  • min - the length must be greater than or equal to this value.
  • max - the length must be less than or equal to this value.
  • message - the message on failure. Defaults to either:
    • must be %{length} byte(s)
    • must be at least %{length} byte(s)
    • must be at most %{length} byte(s)

Examples

iex> validator = Vaxin.validate_string_length(min: 1, max: 20)
iex> Vaxin.validate(validator, "Hello World!")
{:ok, "Hello World!"}
iex> {:error, error} = Vaxin.validate(validator, "")
iex> Exception.message(error)
"must be at least 1 byte(s)"