ExValidator v0.1.0 ExValidator View Source

Helpers for validating and normalizing Elixir data structures.

All the examples below asume that the ExValidator module is imported.

The validation works as simple composable functions, allowing simple validations…

iex> validator = integer(max: 2)
iex> validator.(2)
{:ok, 2}
iex> validator.(4)
{:error, "greater than 2"}

…data casting…

iex> validator = map_of(%{name: string(required: true), age: integer(min: 1)})
iex> validator.(%{"name" => "Jhon", "age": "26"})
{:ok, %{name: "Jhon", age: 26}}
iex> validator.(%{"age": "a"})
{:error, %{name: "is blank", age: "not a number"}}

…and more complex use cases:

iex> address = map_of(%{
...>   city: string(required: true),
...>   state: string(required: true, min: 2, max: 2)
...> })
iex> person = map_of(%{
...>   name: string(required: true),
...>   age: integer(min: 1),
...>   addresses: list_of(address)
...> })
iex> validator = list_of(person)
iex> data = [
...>   %{
...>     "name" => "Jhon",
...>     "age" => "aa",
...>     "addresses" => [
...>       %{"city" => "New York", "state" => "NY"},
...>       %{"city" => "Los Angeles", "state" => "LA"},
...>     ]
...>   },
...>   %{
...>     "name" => "Alex",
...>     "addresses" => [
...>       %{"city" => "Chicago", "states" => "IL"},
...>       %{"city" => "San Francisco", "state" => "CA"},
...>     ]
...>   }
...> ]
iex> validator.(data)
{:error, %{
  0 => %{age: "not a number"},
  1 => %{addresses: %{0 => %{state: "is blank"}}}
}}

Global options

These are the global options shared among all validators:

  • required - validates if the value is present
  • default - default result for when the value is not present

Link to this section Summary

Functions

Checks if any of the given validators allows the value

Validates and parses booleans

Allows for easy validation composition

Validates and parses floats

Validates and parses integers

Validates lists, while stripping nils

Validates maps

Validates and trims strings. Values that implement the String.Chars protocol are converted to strings

Link to this section Types

Link to this type t(result) View Source
t(result) :: (any() -> {:ok, result} | {:error, any()})

Link to this section Functions

Link to this function any_of(validators) View Source
any_of([t(any())]) :: t(any())

Checks if any of the given validators allows the value.

Examples

iex> validator = any_of([integer(required: true), string(required: true, matches: ~r/^foo/)])
iex> validator.(1)
{:ok, 1}
iex> validator.("fooz")
{:ok, "fooz"}
iex> validator.("baaz")
{:error, ["not a number", "no match"]}
iex> validator.(nil)
{:error, ["is blank", "is blank"]}
Link to this function boolean(opts \\ []) View Source
boolean(Keyword.t()) :: t(boolean())

Validates and parses booleans.

The parsing follows the following rule:

  • truthy values: true, “true”, 1, “1”
  • falsey values: false, “false”, 0, “0”

Options

  • message - changes the returned error message

Examples

iex> boolean().(true)
{:ok, true}
iex> boolean().("1")
{:ok, true}
iex> boolean().("true")
{:ok, true}
iex> boolean().("0")
{:ok, false}
iex> boolean().("false")
{:ok, false}
iex> boolean().(nil)
{:ok, nil}
iex> boolean().("yes")
{:error, "not a boolean"}
Link to this function compose(validators) View Source
compose([t(a)]) :: t(a)

Allows for easy validation composition.

Useful for customizing the error message for each kind of error.

Examples

iex> validator = compose([
...>   integer(required: true, message: "WHERE'S THE NUMBER??"),
...>   integer(min: 5, message: "IT'S TOO LOW!!!"),
...>   integer(max: 15, message: "IT'S TOO HIGH!!!"),
...> ])
iex> validator.(10)
{:ok, 10}
iex> validator.(nil)
{:error, "WHERE'S THE NUMBER??"}
iex> validator.(3)
{:error, "IT'S TOO LOW!!!"}
iex> validator.(17)
{:error, "IT'S TOO HIGH!!!"}
Link to this function float(opts \\ []) View Source
float(Keyword.t()) :: t(float())

Validates and parses floats

Options

  • min - validates that the number is equal to or greater than the given value
  • max - validates that the number is equal to or smaller than the given value
  • message - changes the returned error message

Examples

iex> float().(1.0)
{:ok, 1.0}
iex> float().("1")
{:ok, 1.0}
iex> float().("1a")
{:error, "not a number"}
iex> float(message: "whaaaat?").(:foo)
{:error, "whaaaat?"}

iex> float().(nil)
{:ok, nil}
iex> float(required: true).(nil)
{:error, "is blank"}
Link to this function integer(opts \\ []) View Source
integer(Keyword.t()) :: t(integer())

Validates and parses integers.

Options

  • min - validates that the number is equal to or greater than the given value
  • max - validates that the number is equal to or smaller than the given value
  • one_of - validates if the number is contained in the given enum
  • message - changes the returned error message

Examples

iex> integer().(1)
{:ok, 1}
iex> integer().("1")
{:ok, 1}
iex> integer().("1a")
{:error, "not a number"}
iex> integer(message: "what is this?").(:foo)
{:error, "what is this?"}

iex> integer().(nil)
{:ok, nil}
iex> integer(required: true).(nil)
{:error, "is blank"}
Link to this function list_of(validator, opts \\ []) View Source
list_of(t(a), Keyword.t()) :: t([a])

Validates lists, while stripping nils.

Errors are returned as a map containing the index and the respective error.

Options

  • min - validates that the list length is equal to or greater than the given value
  • max - validates that the list length is equal to or smaller than the given value

Examples

iex> list_of(integer()).([1, 2, 3])
{:ok, [1, 2, 3]}
iex> list_of(integer()).("")
{:error, "not a list"}
iex> list_of(integer(min: 5, max: 15)).([3, 4, 7, 8, 10, 13, 17])
{:error, %{0 => "less than 5", 1 => "less than 5", 6 => "greater than 15"}}

iex> list_of(integer()).(nil)
{:ok, nil}
iex> list_of(integer(), required: true).(nil)
{:error, "is blank"}
Link to this function map_of(spec, opts \\ []) View Source
map_of(%{required(atom()) => t(any())}, Keyword.t()) :: t(%{required(atom()) => any()})

Validates maps.

Errors are returned as a map containing the key and the respective error.

Examples

iex> validator = map_of(%{name: string(required: true), age: integer(min: 1)})
iex> validator.(%{name: "Jhon", foo: "Bar"})
{:ok, %{name: "Jhon", age: nil}}
iex> validator.(%{"name" => "Jhon", "age" => "2"})
{:ok, %{name: "Jhon", age: 2}}
iex> validator.(%{age: 0})
{:error, %{name: "is blank", age: "less than 1"}}
iex> validator.(nil)
{:ok, nil}
iex> validator.("")
{:error, "not a map"}

iex> map_of(%{}, required: true).(nil)
{:error, "is blank"}
Link to this function string(opts \\ []) View Source
string(Keyword.t()) :: t(String.t())

Validates and trims strings. Values that implement the String.Chars protocol are converted to strings.

Options

  • min - validates that the string length is equal to or greater than the given value
  • max - validates that the string length is equal to or smaller than the given value
  • one_of - validates that the string is contained in the given enum
  • matches - validates that the string matches the given pattern
  • message - changes the returned error message

Examples

iex> string().("some text")
{:ok, "some text"}
iex> string().("   some text \n")
{:ok, "some text"}
iex> string(matches: ~r/foo|bar/).("fooz")
{:ok, "fooz"}
iex> string(matches: ~r/foo|bar/).("baz")
{:error, "no match"}
iex> string().(%{})
{:error, "not a string"}

iex> string().("")
{:ok, nil}
iex> string(required: true).("")
{:error, "is blank"}