exvalibur v0.10.0 Exvalibur View Source

Exvalibur is the generator for blazingly fast validators of maps based on sets of predefined rules.

Generally speaking, one provides a list of rules in a format of a map:

rules = [
  %{matches: %{currency_pair: "EURUSD"},
    conditions: %{rate: %{min: 1.0, max: 2.0}}},
  %{matches: %{currency_pair: "USDEUR"},
    conditions: %{rate: %{min: 1.2, max: 1.3}}},
]

and calls Exvalibur.validator!/2. The latter will produce a validator module with as many clauses of validate/1 function as we have rules above (plus one handling-all clause.) Once generated, the validate/1 function of the module generated might be called directly on the input data, providing blazingly fast validation based completely on pattern matching and guards.

One should privide at least one match or condition:

iex> rules = [%{matches: %{currency_pair: "EURUSD"}}]
...> Exvalibur.validator!(rules, module_name: Exvalibur.MatchValidator)
...> Exvalibur.MatchValidator.validate(%{currency_pair: "EURUSD", rate: 1.5})
{:ok, %{currency_pair: "EURUSD"}}

iex> rules = [%{conditions: %{rate: %{eq: 1.5}}}]
...> Exvalibur.validator!(rules, module_name: Exvalibur.ConditionValidator)
...> Exvalibur.ConditionValidator.validate(%{currency_pair: "EURGBP", rate: 1.5})
{:ok, %{rate: 1.5}}

iex> rules = [%{foo: :bar}]
...> try do
...>   Exvalibur.validator!(rules, module_name: Exvalibur.RaisingValidator)
...> rescue
...>   e in [Exvalibur.Error] ->
...>   e.reason
...> end
%{empty_rule: %{foo: :bar}}

Exvalibur.validator!/2 Produces the validator module given the set of rules.

Options

  • module_name :: binary() the name of the module to produce; when omitted, it will be looked up in current application options
  • merge :: boolean() when true, the existing rules are taken from the module (if exists) and being merged against current rules
  • flow :: boolean() when true, the underlying module generator uses Flow to process an input

Example

iex> rules = [
...>   %{matches: %{currency_pair: "EURUSD"},
...>     conditions: %{rate: %{min: 1.0, max: 2.0}}}]
...> Exvalibur.validator!(rules, module_name: Exvalibur.Validator)
...> Exvalibur.Validator.validate(%{currency_pair: "EURUSD", rate: 1.5})
{:ok, %{currency_pair: "EURUSD", rate: 1.5}}
iex> Exvalibur.Validator.validate(%{currency_pair: "EURGBP", rate: 1.5})
:error
iex> Exvalibur.Validator.validate(%{currency_pair: "EURUSD", rate: 0.5})
:error
iex> rules = [
...>   %{matches: %{currency_pair: "EURGBP"},
...>     conditions: %{rate: %{min: 1.0, max: 2.0}}}]
...> Exvalibur.validator!(rules, module_name: Exvalibur.Validator)
...> Exvalibur.Validator.validate(%{currency_pair: "EURGBP", rate: 1.5})
{:ok, %{currency_pair: "EURGBP", rate: 1.5}}
iex> Exvalibur.Validator.validate(%{currency_pair: "EURUSD", rate: 1.5})
{:ok, %{currency_pair: "EURUSD", rate: 1.5}}

Unknown conditions

iex> rules = [
...>   %{matches: %{currency_pair: "EURGBP"},
...>     conditions: %{rate: %{perfect: true}}}]
...> try do
...>   Exvalibur.validator!(rules, module_name: Exvalibur.Validator)
...> rescue
...>   e in [Exvalibur.Error] ->
...>   e.reason
...> end
%{unknown_guard: :perfect}

When an unknown guard is passed to the rules conditions, compile-time error is produced

Return value

Generated validate/1 function returns either :error or {:ok, map()}. In a case of successful validation, the map contained values that were indeed validated. Note that in the following example the value for any key is not returned.

iex> rules = [
...>   %{matches: %{currency_pair: "EURGBP"},
...>     conditions: %{
...>       rate: %{min: 1.0, max: 2.0},
...>       source: %{one_of: ["FOO", "BAR"]}}}]
...> Exvalibur.validator!(rules, module_name: Exvalibur.Validator, merge: false)
...> Exvalibur.Validator.validate(%{currency_pair: "EURGBP", any: 42, rate: 1.5, source: "FOO"})
{:ok, %{currency_pair: "EURGBP", rate: 1.5, source: "FOO"}}
iex> Exvalibur.Validator.validate(%{currency_pair: "EURUSD", any: 42, rate: 1.5, source: "BAH"})
:error

Module-based validators

iex> defmodule Validator do
...>   use Exvalibur, rules: [
...>     %{
...>       matches: %{currency_pair: <<"EUR", _ :: binary>>},
...>       conditions: %{foo: %{min: 0, max: 100}},
...>       guards: %{num: num > 0 and num < 100}}]
...> end
iex> Validator.validate(%{currency_pair: "EURUSD", foo: 50, num: 50})
{:ok, %{currency_pair: "EURUSD", foo: 50, num: 50}}
iex> Validator.validate(%{currency_pair: "USDEUR", foo: 50, num: 50})
:error
iex> Validator.validate(%{currency_pair: "EURUSD", foo: -50, num: 50})
:error
iex> Validator.validate(%{currency_pair: "EURUSD", foo: 50, num: -50})
:error

Custom validations

With module-based validators, one might add custom validations to the implementation:

iex> defmodule CustomValidator do
...>   use Exvalibur, rules: [
...>     %{conditions: %{foo: %{min: 0, max: 10}}}]
...>
...>   @impl Exvalibur.Validatable
...>   def custom_validate(%{foo: foo}),
...>     do: if foo == 42, do: {:ok, %{foo: 42}}, else: :error
...> end
iex> CustomValidator.validate(%{foo: 5})
{:ok, %{foo: 5}}
iex> CustomValidator.validate(%{foo: 50})
:error
iex> CustomValidator.validate(%{foo: 42})
{:ok, %{foo: 42}}

Link to this section Summary

Link to this section Functions

Link to this function

validator!(rules, opts) View Source
validator!(rules :: list() | MapSet.t(), opts :: list()) ::
  {:module, module(), binary(), term()}