xema v0.11.0 Xema View Source

A schema validator inspired by JSON Schema.

All available keywords to construct a schema are described on page Usage.

This module can be used to construct a schema module. Should a module contain multiple schemas the option multi: true is required.

use Xema imports Xema.Builder and extends the module with the functions

  • __MODULE__.valid?/2
  • __MODULE__.validate/2
  • __MODULE__.validate!/2
  • __MODULE__.cast/2
  • __MODULE__.cast!/2
  • __MODULE__.xema/1

The macro xema/2 supports the construction of a schema. After that the schema is available via the functions above.

In a multi schema module a schema can be tagged with @default true and then called by

  • __MODULE__.valid?/1
  • __MODULE__.validate/1
  • __MODULE__.validate!/1
  • __MODULE__.cast/1
  • __MODULE__.cast!/1
  • __MODULE__.xema/0

The functions with arity 1 are also available for single schema modules.

Examples

Sinlge schema module:

iex> defmodule SingleSchema do
...>   use Xema
...>
...>   # The name :num is optional.
...>   xema :num, do: number(minimum: 1)
...> end
iex>
iex> SingleSchema.valid?(:num, 6)
true
iex> SingleSchema.valid?(5)
true
iex> SingleSchema.validate(0)
{:error, %Xema.ValidationError{
   reason: %{minimum: 1, value: 0}
}}
iex> SingleSchema.cast("5")
{:ok, 5}
iex> SingleSchema.cast("-5")
{:error, %Xema.ValidationError{
   reason: %{minimum: 1, value: -5}
}}

Multi schema module:

iex> defmodule Schema do
...>   use Xema, multi: true
...>
...>   @pos integer(minimum: 0)
...>   @neg integer(maximum: 0)
...>
...>   @default true
...>   xema :user do
...>     map(
...>       properties: %{
...>         name: string(min_length: 1),
...>         age: @pos
...>       }
...>     )
...>   end
...>
...>   xema :nums do
...>     map(
...>       properties: %{
...>         pos: list(items: @pos),
...>         neg: list(items: @neg)
...>       }
...>     )
...>   end
...> end
iex>
iex> Schema.valid?(:user, %{name: "John", age: 21})
true
iex> Schema.valid?(%{name: "John", age: 21})
true
iex> Schema.valid?(%{name: "", age: 21})
false
iex> Schema.validate(%{name: "John", age: 21})
:ok
iex> Schema.validate(%{name: "", age: 21})
{:error, %Xema.ValidationError{
  reason: %{
    properties: %{name: %{min_length: 1, value: ""}}}
  }
}
iex> Schema.valid?(:nums, %{pos: [1, 2, 3]})
true
iex> Schema.valid?(:nums, %{neg: [1, 2, 3]})
false

Struct schema module:

iex> defmodule StructA do
...>   use Xema
...>
...>   xema do
...>     field :foo, :integer, minimum: 0
...>   end
...> end
...>
...> defmodule StructB do
...>   use Xema
...>
...>   xema do
...>     field :a, :string, min_length: 3
...>     field :b, StructA
...>     required [:a]
...>   end
...> end
...>
...> data = StructB.cast!(a: "abc", b: %{foo: 5})
...> data.a
"abc"
iex> Map.from_struct(data.b)
%{foo: 5}

For more examples to construct schemas see "Examples".

Link to this section Summary

Types

t()

This struct contains the schema and references of the schema.

Functions

Converts the given data using the specified schema. Returns {:ok, result} or {:error, reason}. The result is converted and validated with the schema.

Converts the given data using the specified schema. Returns the converted data or an exception.

Creates a Xema from a JSON Schema. The argument json_schema is expected as a decoded JSON Schema.

This function creates the schema from the given data.

Returns the source for a given xema. The output can differ from the input if the schema contains references. To get the original source the schema must be created with inline: false.

Returns true if the value is a valid value against the given schema; otherwise returns false.

Returns :ok if the value is a valid value against the given schema; otherwise returns an error tuple.

Returns :ok if the value is a valid value against the given schema; otherwise raises a Elixir.Xema.ValidationError.

Link to this section Types

Link to this type

t()

View Source
t() :: %Xema{refs: map(), schema: Xema.Schema.t()}

This struct contains the schema and references of the schema.

Link to this section Functions

Link to this function

cast(xema, value, opts \\ [])

View Source

Converts the given data using the specified schema. Returns {:ok, result} or {:error, reason}. The result is converted and validated with the schema.

Examples:

iex> schema = Xema.new({:integer, minimum: 1})
iex> Xema.cast(schema, "5")
{:ok, 5}
iex> Xema.cast(schema, "five")
{:error, %Xema.CastError{
  key: nil,
  path: [],
  to: :integer,
  value: "five"
}}
iex> Xema.cast(schema, "0")
{:error, %Xema.ValidationError{
  reason: %{minimum: 1, value: 0}
}}

Multiple types

If for a value multiple types are defined the function used the result of the first successful conversion.

Examples

iex> schema = Xema.new([:integer, :string, nil])
iex> Xema.cast(schema, 5)
{:ok, 5}
iex> Xema.cast(schema, 5.5)
{:ok, "5.5"}
iex> Xema.cast(schema, "5")
{:ok, 5}
iex> Xema.cast(schema, "five")
{:ok, "five"}
iex> Xema.cast(schema, nil)
{:ok, nil}
iex> Xema.cast(schema, [5])
{:error,
  %Xema.CastError{path: [], to: [:integer, :string, nil], value: [5]}
}

Cast with any_of, all_of, and one_of

Schemas in a combiner will be cast independently one by one in reverse order.

Examples

iex> schema = Xema.new(any_of: [
...>   [properties: %{a: :integer}],
...>   [properties: %{a: :string}]
...> ])
iex> Xema.cast(schema, %{a: 5})
{:ok, %{a: 5}}
iex> Xema.cast(schema, %{a: 5.5})
{:ok, %{a: "5.5"}}
iex> Xema.cast(schema, %{a: "5"})
{:ok, %{a: 5}}
iex> Xema.cast(schema, %{a: "five"})
{:ok, %{a: "five"}}
iex> Xema.cast(schema, %{a: [5]})
{:error,
  %Xema.CastError{
    error: nil,
    key: nil,
    message: nil,
    path: [],
    to: [
      %{path: [:a], to: :integer, value: [5]},
      %{path: [:a], to: :string, value: [5]}
    ],
    value: %{a: [5]}
}}

Options

With the option additional_properties: :delete additional properties will be deleted on cast. Additional properties will be deleted in schemas with additional_properties: false.

Examples

iex> schema = Xema.new(
...>   properties: %{
...>     a: [
...>       properties: %{
...>         foo: :integer
...>       },
...>       additional_properties: false
...>     ],
...>     b: [
...>       properties: %{
...>         foo: :integer
...>       }
...>     ]
...>   }
...> )
iex>
iex> Xema.cast(schema, %{
...>   a: %{foo: "6", bar: "7"},
...>   b: %{foo: "6", bar: "7"},
...> }, additional_properties: :delete)
{:ok, %{
  a: %{foo: 6},
  b: %{foo: 6, bar: "7"}
}}
Link to this function

cast!(xema, value, opts \\ [])

View Source

Converts the given data using the specified schema. Returns the converted data or an exception.

Link to this function

from_json_schema(json_schema, opts \\ [])

View Source
from_json_schema(atom() | map(), keyword()) :: Xema.t()

Creates a Xema from a JSON Schema. The argument json_schema is expected as a decoded JSON Schema.

All keys that are not standard JSON Schema keywords have to be known atoms. If the schema has additional keys that are unknown atoms the option atom: :force is needed. In this case the atoms will be created. This is not needed for keys expected by JSON Schema (e.g. in properties)

Options:

  • :draft specifies the draft to check the given JSON Schema. Possible values are "draft4", "draft6", and "draft7", default is "draft7". If :draft not set and the schema contains $schema then the value for $schema is used for this option.
  • :atoms creates atoms for unknown atoms when set to :force. This is just needed for additional JSON Schema keywords.

Examples

iex> Xema.from_json_schema(%{"type" => "integer", "minimum" => 5})
%Xema{schema: %Xema.Schema{minimum: 5, type: :integer}}

iex> schema = %{
...>   "type" => "object",
...>   "properties" => %{"foo" => %{"type" => "integer"}}
...> }
iex> Xema.from_json_schema(schema)
%Xema{schema:
  %Xema.Schema{
    properties: %{"foo" => %Xema.Schema{type: :integer}},
    type: :map
  }
}

iex> Xema.from_json_schema(%{"type" => "integer", "foo" => "bar"}, atom: :force)
%Xema{schema: %Xema.Schema{data: %{foo: "bar"}, type: :integer}}

iex> Xema.from_json_schema(%{"exclusiveMaximum" => 5}, draft: "draft7")
%Xema{schema: %Xema.Schema{exclusive_maximum: 5}}

iex> Xema.from_json_schema(%{"exclusiveMaximum" => 5}, draft: "draft4")
** (Xema.SchemaError) Can't build schema:
Dependencies for "exclusiveMaximum" failed. Missing required key "maximum".

This function creates the schema from the given data.

Possible options:

  • :loader - a loader for remote schemas. This option will overwrite the

            loader from the config.
            See [Configure a loader](loader.html) to how to define a loader.
  • inline - inlined all references in the schema. Default :true.

Examples

Simple schema:

iex> schema = Xema.new :string
iex> Xema.valid? schema, "hello"
true
iex> Xema.valid? schema, 42
false

Schema:

iex> schema = Xema.new {:string, min_length: 3, max_length: 12}
iex> Xema.valid? schema, "hello"
true
iex> Xema.valid? schema, "hi"
false

Nested schemas:

iex> schema = Xema.new {:list, items: {:number, minimum: 2}}
iex> Xema.validate(schema, [2, 3, 4])
:ok
iex> Xema.valid?(schema, [2, 3, 4])
true
iex> Xema.validate(schema, [2, 3, 1])
{:error, %Xema.ValidationError{
  reason: %{
    items: [{2, %{value: 1, minimum: 2}}]}
  }
}

More examples can be found on page Usage.

Returns the source for a given xema. The output can differ from the input if the schema contains references. To get the original source the schema must be created with inline: false.

Examples

iex> {:integer, minimum: 1} |> Xema.new() |> Xema.source()
{:integer, minimum: 1}
Link to this function

valid?(schema, value)

View Source
valid?(Xema.t() | Xema.Schema.t(), any()) :: boolean()

Returns true if the value is a valid value against the given schema; otherwise returns false.

Returns :ok if the value is a valid value against the given schema; otherwise returns an error tuple.

Link to this function

validate!(xema, value)

View Source
validate!(Xema.t() | Xema.Schema.t(), any()) :: :ok

Returns :ok if the value is a valid value against the given schema; otherwise raises a Elixir.Xema.ValidationError.