Usage
This page describes all keywords that are available for creating a schema.
All schemas will construct in the "raw" format. The Xema.Builder
provides some
convenience functions to create schemas. The recommended method to construct a
schema is use Xema
described in the module documentation of Xema
.
Type any
The schema any will accept any data.
iex> schema = Xema.new :any
iex> Xema.validate schema, 42
:ok
iex> Xema.validate schema, "foo"
:ok
iex> Xema.validate schema, nil
:ok
Type nil
The nil type matches only nil
.
iex> schema = Xema.new :nil
iex> Xema.validate schema, nil
:ok
iex> {:error, error} = Xema.validate schema, 0
{:error, %Xema.ValidationError{
reason: %{type: :nil, value: 0}
}}
iex> Exception.message(error)
"Expected nil, got 0."
Type boolean
The boolean type matches only true
and false
.
iex> schema = Xema.new :boolean
iex> Xema.validate schema, true
:ok
iex> Xema.valid? schema, false
true
iex> {:error, error} = Xema.validate schema, 0
{:error, %Xema.ValidationError{
reason: %{type: :boolean, value: 0}
}}
iex> Exception.message(error)
"Expected :boolean, got 0."
iex> Xema.valid? schema, nil
false
Type atom
The atom type matches only atoms. Schemas of type atom match also the atoms
true
, false
, and nil
.
iex> schema = Xema.new :atom
iex> Xema.validate schema, :foo
:ok
iex> Xema.valid? schema, "foo"
false
iex> {:error, error} = Xema.validate schema, 0
{:error, %Xema.ValidationError{
reason: %{type: :atom, value: 0}
}}
iex> Exception.message(error)
"Expected :atom, got 0."
iex> Xema.valid? schema, nil
true
iex> Xema.valid? schema, false
true
Type string
JSON Schema Draft: 4/6/7
The string type is used for strings.
iex> schema = Xema.new :string
iex> Xema.validate schema, "José"
:ok
iex> {:error, error} = Xema.validate schema, 42
{:error, %Xema.ValidationError{
reason: %{type: :string, value: 42}
}}
iex> Exception.message(error)
"Expected :string, got 42."
iex> Xema.valid? schema, "José"
true
iex> Xema.valid? schema, 42
false
Length
The length of a string can be constrained using the min_length
and max_length
keywords. For both keywords, the value must be a non-negative number.
iex> schema = Xema.new {:string, min_length: 2, max_length: 3}
iex> {:error, error} = Xema.validate schema, "a"
{:error, %Xema.ValidationError{
reason: %{value: "a", min_length: 2}
}}
iex> Exception.message(error)
~s|Expected minimum length of 2, got "a".|
iex> Xema.validate schema, "ab"
:ok
iex> Xema.validate schema, "abc"
:ok
iex> {:error, error} = Xema.validate schema, "abcd"
{:error, %Xema.ValidationError{
reason: %{value: "abcd", max_length: 3}
}}
iex> Exception.message(error)
~s|Expected maximum length of 3, got "abcd".|
Regular Expression
The pattern
keyword is used to restrict a string to a particular regular
expression.
iex> schema = Xema.new {:string, pattern: ~r/[0-9]-[A-B]+/}
iex> Xema.validate schema, "1-AB"
:ok
iex> {:error, error} = Xema.validate schema, "foo"
{:error, %Xema.ValidationError{
reason: %{value: "foo", pattern: ~r/[0-9]-[A-B]+/}
}}
iex> Exception.message(error)
~s|Pattern ~r/[0-9]-[A-B]+/ does not match value "foo".|
The regular expression can also be a string.
iex> schema = Xema.new {:string, pattern: "[0-9]-[A-B]+"}
iex> Xema.validate schema, "1-AB"
:ok
iex> {:error, error} = Xema.validate schema, "foo"
{:error, %Xema.ValidationError{
reason: %{value: "foo", pattern: ~r/[0-9]-[A-B]+/}
}}
iex> Exception.message(error)
~s|Pattern ~r/[0-9]-[A-B]+/ does not match value "foo".|
Format
JSON Schema Draft: 4/6/7
Basic semantic validation of strings.
:date_time
validation as defined by RFC 3339iex> schema = Xema.new {:string, format: :date_time} iex> Xema.valid? schema, "today" false iex> Xema.valid? schema, "1963-06-19T08:30:06.283185Z" true
:email
validation as defined by RFC 5322iex> {:string, format: :email} ...> |> Xema.new() ...> |> Xema.valid?("marion.mustermann@otto.net") true
:host
checks if thestring
is an valid IPv4, IPv6, or hostname.:hostname
validation as defined by RFC 1034:ipv4
validation as defined by RFC 2673:ipv6
validation as defined by RFC 2373:uri
validation as defindex by RFC 3986:uri_fragment
:uri_path
:uri_query
:uri_userinfo
JSON Schema Draft: -/-/7
:regex
checks if thestring
is a valid regular expression.
Types number, integer and float
There are three numeric types in Xema: number
, integer
and float
. They
share the same validation keywords.
The number
type is used for numbers.
iex> schema = Xema.new :number
iex> Xema.validate schema, 42
:ok
iex> Xema.validate schema, 21.5
:ok
iex> {:error, error} = Xema.validate schema, "foo"
{:error, %Xema.ValidationError{
reason: %{type: :number, value: "foo"}
}}
iex> Exception.message(error)
~s|Expected :number, got "foo".|
The integer
type is used for integral numbers.
iex> schema = Xema.new :integer
iex> Xema.validate schema, 42
:ok
iex> {:error, error} = Xema.validate schema, 21.5
{:error, %Xema.ValidationError{
reason: %{type: :integer, value: 21.5}
}}
iex> Exception.message(error)
"Expected :integer, got 21.5."
The float
type is used for floating point numbers.
iex> schema = Xema.new :float
iex> {:error, error} = Xema.validate schema, 42
{:error, %Xema.ValidationError{
reason: %{type: :float, value: 42}
}}
iex> Exception.message(error)
"Expected :float, got 42."
iex> Xema.validate schema, 21.5
:ok
Multiples
JSON Schema Draft: 4/6/7
Numbers can be restricted to a multiple of a given number, using the
multiple_of
keyword. It may be set to any positive number.
iex> schema = Xema.new {:number, multiple_of: 2}
iex> Xema.validate schema, 8
:ok
iex> {:error, error} = Xema.validate schema, 7
{:error, %Xema.ValidationError{
reason: %{value: 7, multiple_of: 2}
}}
iex> Exception.message(error)
"Value 7 is not a multiple of 2."
iex> Xema.valid? schema, 8.0
true
Range
JSON Schema Draft: 4/-/-
Ranges of numbers are specified using a combination of the minimum
, maximum
,
exclusive_minimum
and exclusive_maximum
keywords.
minimum
specifies a minimum numeric value.exclusive_minimum
is a boolean. When true, it indicates that the range excludes the minimum value, i.e., x > minx > min. When false (or not included), it indicates that the range includes the minimum value, i.e., x≥minx≥min.maximum
specifies a maximum numeric value.exclusive_maximum
is a boolean. When true, it indicates that the range excludes the maximum value, i.e., x < maxx < max. When false (or not included), it indicates that the range includes the maximum value, i.e., x ≤ maxx ≤ max.
iex> schema = Xema.new {
...> :float,
...> minimum: 1.2, maximum: 1.4, exclusive_maximum: true
...> }
iex> {:error, error} = Xema.validate schema, 1.1
{:error, %Xema.ValidationError{
reason: %{value: 1.1, minimum: 1.2}
}}
iex> Exception.message(error)
"Value 1.1 is less than minimum value of 1.2."
iex> Xema.validate schema, 1.2
:ok
iex> Xema.valid? schema, 1.3
true
iex> {:error, error} = Xema.validate schema, 1.4
{:error, %Xema.ValidationError{
reason: %{value: 1.4, maximum: 1.4, exclusive_maximum: true}
}}
iex> Exception.message(error)
"Value 1.4 equals exclusive maximum value of 1.4."
iex> {:error, error} = Xema.validate schema, 1.5
{:error, %Xema.ValidationError{
reason: %{value: 1.5, maximum: 1.4, exclusive_maximum: true}
}}
iex> Exception.message(error)
"Value 1.5 exceeds maximum value of 1.4."
JSON Schema Draft: 6/7
The keywords exclusive_maximum
and exclusive_minimum
changed from a boolean
to a number. Wherever one of these would be true
before, they have now the
value of the corresponding keyword maximum
or minimum
. The keyword
maximum
/minimum
can be removed.
iex> schema = Xema.new {:float, minimum: 1.2, exclusive_maximum: 1.4}
iex> {:error, error} = Xema.validate schema, 1.1
{:error, %Xema.ValidationError{
reason: %{value: 1.1, minimum: 1.2}
}}
iex> Exception.message(error)
"Value 1.1 is less than minimum value of 1.2."
iex> Xema.validate schema, 1.2
:ok
iex> Xema.valid? schema, 1.3
true
iex> {:error, error} = Xema.validate schema, 1.4
{:error, %Xema.ValidationError{
reason: %{value: 1.4, exclusive_maximum: 1.4}
}}
iex> Exception.message(error)
"Value 1.4 equals exclusive maximum value of 1.4."
iex> {:error, error} = Xema.validate schema, 1.5
{:error, %Xema.ValidationError{
reason: %{value: 1.5, exclusive_maximum: 1.4}
}}
iex> Exception.message(error)
"Value 1.5 exceeds maximum value of 1.4."
Type list
List are used for ordered elements, each element may be of a different type.
iex> schema = Xema.new :list
iex> Xema.valid? schema, [1, "two", 3.0]
true
iex> {:error, error} = Xema.validate schema, 9
{:error, %Xema.ValidationError{
reason: %{type: :list, value: 9}
}}
iex> Exception.message(error)
"Expected :list, got 9."
Items
The items
keyword will be used to validate all items of a list to a single
schema.
iex> schema = Xema.new {:list, items: :string}
iex> Xema.valid? schema, ["a", "b", "abc"]
true
iex> {:error, error} = Xema.validate schema, ["a", 1]
{:error, %Xema.ValidationError{
reason: %{items: %{1 => %{type: :string, value: 1}}}
}}
iex> Exception.message(error)
"Expected :string, got 1, at [1]."
The next example shows how to add keywords to the items schema.
iex> schema = Xema.new {:list, items: {:integer, minimum: 1, maximum: 10}}
iex> Xema.validate schema, [1, 2, 3]
:ok
iex> {:error, error} = Xema.validate schema, [3, 2, 1, 0]
{:error, %Xema.ValidationError{
reason: %{items: %{3 => %{value: 0, minimum: 1}}}
}}
iex> Exception.message(error)
"Value 0 is less than minimum value of 1, at [3]."
items
can also be used to give each item a specific schema.
iex> schema = Xema.new {
...> :list,
...> items: [:integer, {:string, min_length: 5}]
...> }
iex> Xema.valid? schema, [1, "hello"]
true
iex> {:error, error} = Xema.validate schema, [1, "five"]
{:error, %Xema.ValidationError{
reason: %{items: %{1 => %{value: "five", min_length: 5}}}
}}
iex> Exception.message(error)
~s|Expected minimum length of 5, got "five", at [1].|
# It’s okay to not provide all of the items:
iex> Xema.validate schema, [1]
:ok
# And, by default, it’s also okay to add additional items to end:
iex> Xema.validate schema, [1, "hello", "foo"]
:ok
Additional Items
The additional_items
keyword controls whether it is valid to have additional
items in the array beyond what is defined in the schema.
iex> schema = Xema.new {
...> :list,
...> items: [:integer, {:string, min_length: 5}],
...> additional_items: false
...> }
iex> Xema.validate schema, [1]
:ok
# It’s okay to not provide all of the items:
# But, since additionalItems is false, we can’t provide extra items:
iex> {:error, error} = Xema.validate schema, [1, "hello", "foo"]
{:error, %Xema.ValidationError{
reason: %{items: %{2 => %{additional_items: false}}}
}}
iex> Exception.message(error)
"Unexpected additional item, at [2]."
iex> {:error, error} = Xema.validate schema, [1, "hello", "foo", "bar"]
{:error, %Xema.ValidationError{
reason: %{
items: %{
2 => %{additional_items: false},
3 => %{additional_items: false}
}
}
}}
iex> Exception.message(error)
"""
Unexpected additional item, at [2].
Unexpected additional item, at [3].\
"""
The keyword can also contain a schema to specify the type of additional items.
iex> schema = Xema.new {
...> :list,
...> items: [:integer, {:string, min_length: 3}],
...> additional_items: :integer
...> }
iex> Xema.valid? schema, [1, "two", 3, 4]
true
iex> {:error, error} = Xema.validate schema, [1, "two", 3, "four"]
{:error, %Xema.ValidationError{
reason: %{items: %{3 => %{type: :integer, value: "four"}}}
}}
iex> Exception.message(error)
~s|Expected :integer, got "four", at [3].|
Length
The length of the array can be specified using the min_items
and max_items
keywords. The value of each keyword must be a non-negative number.
iex> schema = Xema.new {:list, min_items: 2, max_items: 3}
iex> {:error, error} = Xema.validate schema, [1]
{:error, %Xema.ValidationError{
reason: %{value: [1], min_items: 2}
}}
iex> Exception.message(error)
"Expected at least 2 items, got [1]."
iex> Xema.validate schema, [1, 2]
:ok
iex> Xema.validate schema, [1, 2, 3]
:ok
iex> {:error, error} = Xema.validate schema, [1, 2, 3, 4]
{:error, %Xema.ValidationError{
reason: %{value: [1, 2, 3, 4], max_items: 3}
}}
iex> Exception.message(error)
"Expected at most 3 items, got [1, 2, 3, 4]."
Uniqueness
A schema can ensure that each of the items in an array is unique.
iex> schema = Xema.new {:list, unique_items: true}
iex> Xema.valid? schema, [1, 2, 3]
true
iex> {:error, error} = Xema.validate schema, [1, 2, 3, 2, 1]
{:error, %Xema.ValidationError{
reason: %{value: [1, 2, 3, 2, 1], unique_items: true}
}}
iex> Exception.message(error)
"Expected unique items, got [1, 2, 3, 2, 1]."
Type tuple
Tuples are intended as fixed-size containers for multiple elements. The validation of tuples is similar to lists.
iex> schema = Xema.new {:tuple, min_items: 2, max_items: 3}
iex> {:error, error} = Xema.validate schema, {1}
{:error, %Xema.ValidationError{
reason: %{value: {1}, min_items: 2}
}}
iex> Exception.message(error)
"Expected at least 2 items, got {1}."
iex> Xema.validate schema, {1, 2}
:ok
iex> Xema.validate schema, {1, 2, 3}
:ok
iex> {:error, error} = Xema.validate schema, {1, 2, 3, 4}
{:error, %Xema.ValidationError{
reason: %{value: {1, 2, 3, 4}, max_items: 3}
}}
iex> Exception.message(error)
"Expected at most 3 items, got {1, 2, 3, 4}."
Type map
Whenever you need a key-value store, maps are the “go to” data structure in Elixir. Each of these pairs is conventionally referred to as a “property”.
iex> schema = Xema.new :map
iex> Xema.valid? schema, %{"foo" => "bar"}
true
iex> {:error, error} = Xema.validate schema, "bar"
{:error, %Xema.ValidationError{
reason: %{type: :map, value: "bar"}
}}
iex> Exception.message(error)
~s|Expected :map, got "bar".|
# Using non-strings as keys are also valid:
iex> Xema.valid? schema, %{foo: "bar"}
true
iex> Xema.valid? schema, %{1 => "bar"}
true
Keys
The keyword keys
can restrict the keys to atoms or strings.
Atoms as keys:
iex> schema = Xema.new {:map, keys: :atoms}
iex> Xema.valid? schema, %{"foo" => "bar"}
false
iex> Xema.valid? schema, %{foo: "bar"}
true
iex> Xema.valid? schema, %{1 => "bar"}
false
Strings as keys:
iex> schema = Xema.new {:map, keys: :strings}
iex> Xema.valid? schema, %{"foo" => "bar"}
true
iex> Xema.valid? schema, %{foo: "bar"}
false
iex> Xema.valid? schema, %{1 => "bar"}
false
Properties
The properties on a map are defined using the properties
keyword. The value
of properties is a map, where each key is the name of a property and each
value is a schema used to validate that property.
iex> schema = Xema.new {:map,
...> properties: %{
...> a: :integer,
...> b: {:string, min_length: 5}
...> }
...> }
iex> Xema.valid? schema, %{a: 5, b: "hello"}
true
iex> {:error, error} = Xema.validate schema, %{a: 5, b: "ups"}
{:error, %Xema.ValidationError{
reason: %{
properties: %{
b: %{value: "ups", min_length: 5}
}
}
}}
iex> Exception.message(error)
"Expected minimum length of 5, got \"ups\", at [:b]."
# Additinonal properties are allowed by default:
iex> Xema.valid? schema, %{a: 5, b: "hello", add: :prop}
true
Required Properties
By default, the properties defined by the properties keyword are not required.
However, one can provide a list of required
properties using the required
keyword.
iex> schema = Xema.new {:map, properties: %{foo: :string}, required: [:foo]}
iex> Xema.validate schema, %{foo: "bar"}
:ok
iex> {:error, error} = Xema.validate schema, %{bar: "foo"}
{:error, %Xema.ValidationError{
reason: %{required: [:foo]}
}}
iex> Exception.message(error)
"Required properties are missing: [:foo]."
Additional Properties
The additional_properties
keyword is used to control the handling of extra
stuff, that is, properties whose names are not listed in the properties keyword.
By default any additional properties are allowed.
The additional_properties
keyword may be either a boolean or an schema. If
additional_properties
is a boolean and set to false, no additional properties
will be allowed.
iex> schema = Xema.new {:map,
...> properties: %{foo: :string},
...> required: [:foo],
...> additional_properties: false
...> }
iex> Xema.validate schema, %{foo: "bar"}
:ok
iex> {:error, error} = Xema.validate schema, %{foo: "bar", bar: "foo"}
{:error, %Xema.ValidationError{
reason: %{
properties: %{bar: %{additional_properties: false}}
}
}}
iex> Exception.message(error)
"Expected only defined properties, got key [:bar]."
additional_properties
can also contain a schema to specify the type of
additional properites.
iex> schema = Xema.new {
...> :map,
...> properties: %{foo: :string},
...> additional_properties: :integer
...> }
iex> Xema.valid? schema, %{foo: "foo", add: 1}
true
iex> {:error, error} = Xema.validate schema, %{foo: "foo", add: "one"}
{:error, %Xema.ValidationError{
reason: %{properties: %{add: %{type: :integer, value: "one"}}}
}}
iex> Exception.message(error)
~s|Expected :integer, got "one", at [:add].|
Pattern Properties
The keyword pattern_properties
defined additional properties by regular
expressions.
iex> schema = Xema.new {
...> :map,
...> additional_properties: false,
...> pattern_properties: %{
...> ~r/^s_/ => :string,
...> ~r/^i_/ => :integer
...> }
...> }
iex> Xema.valid? schema, %{"s_0" => "foo", "i_1" => 6}
true
iex> Xema.valid? schema, %{s_0: "foo", i_1: 6}
true
iex> {:error, error} = Xema.validate schema, %{s_0: "foo", f_1: 6.6}
{:error, %Xema.ValidationError{
reason: %{properties: %{f_1: %{additional_properties: false}}}
}}
iex> Exception.message(error)
"Expected only defined properties, got key [:f_1]."
Size
The number of properties on an object can be restricted using the
min_properties
and max_properties
keywords.
iex> schema = Xema.new {:map,
...> min_properties: 2,
...> max_properties: 3
...> }
iex> Xema.valid? schema, %{a: 1, b: 2}
true
iex> {:error, error} = Xema.validate schema, %{}
{:error, %Xema.ValidationError{
reason: %{min_properties: 2, value: %{}}
}}
iex> Exception.message(error)
"Expected at least 2 properties, got %{}."
iex> {:error, error} = Xema.validate schema, %{a: 1, b: 2, c: 3, d: 4}
{:error, %Xema.ValidationError{
reason: %{max_properties: 3, value: %{a: 1, b: 2, c: 3, d: 4}}
}}
iex> Exception.message(error)
"Expected at most 3 properties, got %{a: 1, b: 2, c: 3, d: 4}."
Size
The type of a key in the schema also matters in validation.
iex> schema = Xema.new {:map,
...> properties: %{foo: :integer},
...> additional_properties: false
...> }
iex> Xema.valid? schema, %{foo: 1}
true
iex> {:error, error} = Xema.validate schema, %{"foo" => 1}
{:error, %Xema.ValidationError{
reason: %{properties: %{"foo" => %{additional_properties: false}}}
}}
iex> Exception.message(error)
~s|Expected only defined properties, got key [\"foo\"].|
Dependencies
The dependencies
keyword allows the schema of the object to change based on
the presence of certain special properties.
iex> schema = Xema.new {
...> :map,
...> properties: %{
...> a: :number,
...> b: :number,
...> c: :number
...> },
...> dependencies: %{
...> b: [:c]
...> }
...> }
iex> Xema.valid? schema, %{a: 5}
true
iex> Xema.valid? schema, %{c: 9}
true
iex> Xema.valid? schema, %{b: 1}
false
iex> Xema.valid? schema, %{b: 1, c: 7}
true
Type struct
Structs can also be validated.
iex> schema = Xema.new :struct
iex> Xema.valid? schema, ~r/.*/
true
iex> Xema.valid? schema, %{}
false
Module
The module
keyword allows specifing which struct is expected.
iex> schema = Xema.new {:struct, module: Regex}
iex> Xema.valid? schema, ~r/.*/
true
iex> Xema.valid? schema, URI.parse("")
false
Map keywords for structs
The validations for map
are also available.
The next example shows a schema for an URI that needs a fragment.
iex> schema = Xema.new {
...> :struct, module: URI, properties: %{fragment: :string}
...> }
iex>
iex> Xema.valid?(schema, URI.parse("http://example.com"))
false
iex> Xema.valid?(schema, URI.parse("http://example.com#frag"))
true
Multiples Types
JSON Schema Draft: 4/6/7
It is also possible to check if a value matches one of several types.
iex> schema = Xema.new {[:string, nil], min_length: 1}
iex> Xema.valid? schema, "foo"
true
iex> Xema.valid? schema, nil
true
iex> Xema.valid? schema, ""
false
Allow Additional Types
JSON Schema Draft: -
The keyword allow
adds an extra type to the schema validation.
iex> schema = Xema.new {:string, min_length: 1, allow: nil}
iex> Xema.valid? schema, "foo"
true
iex> Xema.valid? schema, nil
true
iex> Xema.valid? schema, ""
false
Constants
JSON Schema Draft: -/6/7
This keyword checks if a value is equals to the given const
.
iex> schema = Xema.new(const: 4711)
iex> Xema.validate schema, 4711
:ok
iex> {:error, error} = Xema.validate schema, 333
{:error, %Xema.ValidationError{
reason: %{const: 4711, value: 333}
}}
iex> Exception.message(error)
"Expected 4711, got 333."
Enumerations
The enum
keyword is used to restrict a value to a fixed set of values. It must
be an array with at least one element, where each element is unique.
iex> schema = Xema.new {:any, enum: [1, "foo", :bar]}
iex> Xema.valid? schema, :bar
true
iex> Xema.valid? schema, 42
false
Negate Schema
The keyword not
negates a schema.
iex> schema = Xema.new(not: {:integer, minimum: 0})
iex> Xema.valid? schema, 10
false
iex> Xema.valid? schema, -10
true
If-Then-Else
The keywords if
, then
, else
work together to implement conditional
application of a subschema based on the outcome of another subschema.
iex> schema =
...> Xema.new(
...> if: :list,
...> then: [items: :integer, min_items: 2],
...> else: :integer
...> )
...>
...> Xema.valid?(schema, 3)
true
iex> Xema.valid?(schema, "3")
false
iex> Xema.valid?(schema, [1])
false
iex> Xema.valid?(schema, [1, 2])
true
Custom validator
With the keyword validator
a custom validator can be defined. The validator
expected a function or a tuple of module and function name. The validator
function gets the current value and return :ok on success and an error tuple
on failure.
iex> defmodule Palindrome do
...> @xema Xema.new(
...> properties: %{
...> palindrome: {:string, validator: &Palindrome.check/1}
...> }
...> )
...>
...> def check(value) do
...> case value == String.reverse(value) do
...> true -> :ok
...> false -> {:error, :no_palindrome}
...> end
...> end
...>
...> def validate(value) do
...> Xema.validate(@xema, value)
...> end
...> end
iex>
iex> Palindrome.validate(%{palindrome: "abba"})
:ok
iex> {:error, error} = Palindrome.validate(%{palindrome: "beatles"})
{:error, %Xema.ValidationError{
reason: %{
properties: %{
palindrome: %{validator: :no_palindrome, value: "beatles"}
}
}
}}
iex> Exception.message(error)
~s|Validator fails with :no_palindrome for value "beatles", at [:palindrome].|
Combine Schemas
The keywords all_of
, any_of
, and one_of
combines schemas.
With all_of
all schemas have to match.
iex> all = Xema.new(all_of: [
...> {:integer, multiple_of: 2},
...> {:integer, multiple_of: 3}
...> ])
iex> 0..9 |> Enum.map(&Xema.valid?(all, &1)) |> Enum.with_index()
[true: 0, false: 1, false: 2, false: 3, false: 4,
false: 5, true: 6, false: 7, false: 8, false: 9]
With any_of
any schema have to match.
iex> any = Xema.new(any_of: [
...> {:integer, multiple_of: 2},
...> {:integer, multiple_of: 3}
...> ])
iex> 0..9 |> Enum.map(&Xema.valid?(any, &1)) |> Enum.with_index()
[true: 0, false: 1, true: 2, true: 3, true: 4,
false: 5, true: 6, false: 7, true: 8, true: 9]
With one_of
exactly on schema have to match.
iex> one = Xema.new(one_of: [
...> {:integer, multiple_of: 2},
...> {:integer, multiple_of: 3}
...> ])
iex> 0..9 |> Enum.map(&Xema.valid?(one, &1)) |> Enum.with_index()
[false: 0, false: 1, true: 2, true: 3, true: 4,
false: 5, false: 6, false: 7, true: 8, true: 9]
Structuring a schema
This section will present some options to structuring and reuse schemas.
definitions
and ref
To reuse a schema put it under the keyword definitions
. Later on, the schema
can be referenced with the keyword ref
.
iex> schema = Xema.new {
...> :map,
...> definitions: %{
...> positive: {:integer, minimum: 1},
...> negative: {:integer, maximum: -1}
...> },
...> properties: %{
...> a: {:ref, "#/definitions/positive"},
...> b: {:ref, "#/definitions/positive"},
...> c: {:ref, "#/definitions/negative"},
...> # You can also reference any other schema.
...> d: {:ref, "#/properties/c"}
...> }
...> }
...> Xema.validate schema, %{a: 1, c: -1}
:ok
iex> {:error, error} = Xema.validate schema, %{b: 1, c: 1}
{:error, %Xema.ValidationError{
reason: %{properties: %{c: %{maximum: -1, value: 1}}}
}}
iex> Exception.message(error)
"Value 1 exceeds maximum value of -1, at [:c]."
iex> Xema.validate schema, %{d: -1}
:ok
iex> {:error, error} = Xema.validate schema, %{d: 1}
{:error, %Xema.ValidationError{
reason: %{properties: %{d: %{maximum: -1, value: 1}}}
}}
iex> Exception.message(error)
"Value 1 exceeds maximum value of -1, at [:d]."
Without definitions
and ref
It is also possible to use a schema in another schema, as in the following code.
iex> positive = Xema.new {:integer, minimum: 1}
...> negative = Xema.new {:integer, maximum: -1}
...> schema = Xema.new {
...> :map,
...> properties: %{
...> a: positive,
...> b: positive,
...> c: negative
...> }
...> }
...> Xema.validate schema, %{a: 1, b: 2, c: -3}
:ok
In multiple files
To structure schemas in multiple files you have to configure a loader to laod the files. The section "Configure a loader" described the configuration and implementation of an loader.
Let's assume you have the following file available at
https://localhost:1234/positive.exon
.
{
:string,
definitions: %{
or_nil: {:any_of, [:nil, {:ref, "#"}]}
}
}
I admit this example is a little absurd. But the URL could be also any other URL and with another loader, the file could be on your hard disk.
You can use the schema above as follow.
iex> alias Test.RemoteLoaderExon
iex> schema = Xema.new({
...> :map,
...> id: "http://localhost:1234",
...> properties: %{
...> name: {:ref, "xema_name.exon#/definitions/or_nil"},
...> str: {:ref, "xema_name.exon"}
...> }
...> }, loader: RemoteLoaderExon)
iex> Xema.validate schema, %{str: "foo"}
:ok
iex> {:error, error} = Xema.validate schema, %{str: nil}
{:error, %Xema.ValidationError{
reason: %{properties: %{str: %{type: :string, value: nil}}}
}}
iex> Exception.message(error)
"Expected :string, got nil, at [:str]."
iex> Xema.validate schema, %{name: nil}
:ok
iex> Xema.validate schema, %{name: "Funny van Dannen"}
:ok
iex> {:error, error} = Xema.validate schema, %{name: 66}
{:error,
%Xema.ValidationError{
reason: %{
properties: %{
name: %{any_of: [
%{type: nil, value: 66},
%{type: :string, value: 66}
],
value: 66}
}
}
}
}
iex> Exception.message(error)
"""
No match of any schema, at [:name].
Expected nil, got 66, at [:name].
Expected :string, got 66, at [:name].\
"""