README.md

# Xema
[![Build Status](https://travis-ci.org/hrzndhrn/xema.svg?branch=master)](https://travis-ci.org/hrzndhrn/xema)
[![Coverage Status](https://coveralls.io/repos/github/hrzndhrn/xema/badge.svg?branch=master)](https://coveralls.io/github/hrzndhrn/xema?branch=master)
[![Hex.pm](https://img.shields.io/hexpm/v/xema.svg)](https://hex.pm/packages/xema)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Xema is a schema validator inspired by [JSON Schema](http://json-schema.org).

Xema allows you to annotate and validate elixir data structures.

Xema is in early beta. If you try it and has an issue, report them.

## Installation

First, add Xema to your `mix.exs` dependencies:

```elixir
def deps do
  [{:xema, "~> 0.2"}]
end
```

Then, update your dependencies:

```Shell
$ mix deps.get
```

## Usage

Xema supported the following types to validate data structures.

* [Type any](#any)
* [Type nil](#nil)
* [Type boolean](#boolean)
* [Type string](#string)
  * [Length](#length)
  * [Regular Expression](#regex)
* [Types number, integer and float](#number)
  * [Multiples](#multi)
  * [Range](#range)
* [Type list](#list)
  * [Items](#items)
  * [Additional Items](#additional_items)
  * [Length](#list_length)
  * [Uniqueness](#unique)
* [Type map](#map)
  * [Keys](#keys)
  * [Properties](#properties)
  * [Required Properties](#required_properties)
  * [Additional Properties](#additional_properties)
  * [Pattern Properties](#pattern_properties)
  * [Size](#map_size)
* [Enumerations](#enum)

### <a name="any"></a> Type any

The schema any will accept any data.

```elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :any
%Xema{content: %Xema.Schema{type: :any, as: :any}}
iex> validate schema, 42
:ok
iex> validate schema, "foo"
:ok
iex> validate schema, nil
:ok
```

### <a name="nil"></a> Type nil

The nil type matches only `nil`.

```elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :nil
%Xema{content: %Xema.Schema{type: :nil, as: :nil}}
iex> validate schema, nil
:ok
iex> validate schema, 0
{:error, %{type: :nil, value: 0}}
```

### <a name="boolean"></a> Type boolean

The boolean type matches only `true` and `false`.
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :boolean
%Xema{content: %Xema.Schema{type: :boolean, as: :boolean}}
iex> validate schema, true
:ok
iex> is_valid? schema, false
true
iex> validate schema, 0
{:error, %{type: :boolean, value: 0}}
iex> is_valid? schema, nil
false
```

### <a name="string"></a> Type string

The string type is used for strings.

```elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :string
%Xema{content: %Xema.Schema{type: :string, as: :string}}
iex> validate schema, "José"
:ok
iex> validate schema, 42
{:error, %{type: :string, value: 42}}
iex> is_valid? schema, "José"
true
iex> is_valid? schema, 42
false
```

#### <a name="length"></a> 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.

```elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :string, min_length: 2, max_length: 3
%Xema{content:
  %Xema.Schema{min_length: 2, max_length: 3, type: :string, as: :string}
}
iex> validate schema, "a"
{:error, %{value: "a", min_length: 2}}
iex> validate schema, "ab"
:ok
iex> validate schema, "abc"
:ok
iex> validate schema, "abcd"
{:error, %{value: "abcd", max_length: 3}}
```

#### <a name="regex"></a> Regular Expression

The `pattern` keyword is used to restrict a string to a particular regular
expression.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :string, pattern: ~r/[0-9]-[A-B]+/
%Xema{content: %Xema.Schema{type: :string, as: :string, pattern: ~r/[0-9]-[A-B]+/}}
iex> validate schema, "1-AB"
:ok
iex> validate schema, "foo"
{:error, %{value: "foo", pattern: ~r/[0-9]-[A-B]+/}}
```

### <a name="number"></a> 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.
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :number
%Xema{content: %Xema.Schema{type: :number, as: :number}}
iex> validate schema, 42
:ok
iex> validate schema, 21.5
:ok
iex> validate schema, "foo"
{:error, %{type: :number, value: "foo"}}
```

The `integer` type is used for integral numbers.
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :integer
%Xema{content: %Xema.Schema{type: :integer, as: :integer}}
iex> validate schema, 42
:ok
iex> validate schema, 21.5
{:error, %{type: :integer, value: 21.5}}
```

The `float` type is used for floating point numbers.
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :float
%Xema{content: %Xema.Schema{type: :float, as: :float}}
iex> validate schema, 42
{:error, %{type: :float, value: 42}}
iex> validate schema, 21.5
:ok
```

#### <a name="multi"></a> Multiples
Numbers can be restricted to a multiple of a given number, using the
`multiple_of` keyword. It may be set to any positive number.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :number, multiple_of: 2
%Xema{content: %Xema.Schema{type: :number, as: :number, multiple_of: 2}}
iex> validate schema, 8
:ok
iex> validate schema, 7
{:error, %{value: 7, multiple_of: 2}}
iex> is_valid? schema, 8.0
true
```

#### <a name="range"></a> Range
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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :float, minimum: 1.2, maximum: 1.4, exclusive_maximum: true
%Xema{content: %Xema.Schema{
  type: :float,
  as: :float,
  minimum: 1.2,
  maximum: 1.4,
  exclusive_maximum: true
}}
iex> validate schema, 1.1
{:error, %{value: 1.1, minimum: 1.2}}
iex> validate schema, 1.2
:ok
iex> is_valid? schema, 1.3
true
iex> validate schema, 1.4
{:error, %{value: 1.4, maximum: 1.4, exclusive_maximum: true}}
iex> validate schema, 1.5
{:error, %{value: 1.5, maximum: 1.4, exclusive_maximum: true}}
```

### <a name="list"></a> Type list
List are used for ordered elements, each element may be of a different type.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list
%Xema{content: %Xema.Schema{type: :list, as: :list}}
iex> is_valid? schema, [1, "two", 3.0]
true
iex> validate schema, 9
{:error, %{type: :list, value: 9}}
```

#### <a name="items"></a> Items
The `items` keyword will be used to validate all items of a list to a single
schema.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list, items: :string
%Xema{content: %Xema.Schema{
  type: :list,
  as: :list,
  items: %Xema.Schema{type: :string, as: :string}
}}
iex> is_valid? schema, ["a", "b", "abc"]
true
iex> validate schema, ["a", 1]
{:error, [%{
  at: 1,
  error: %{type: :string, value: 1}
}]}
```

The next example shows how to add keywords to the items schema.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list, items: {:integer, minimum: 1, maximum: 10}
%Xema{content: %Xema.Schema{
  type: :list,
  as: :list,
  items: %Xema.Schema{type: :integer, as: :integer, minimum: 1, maximum: 10}
}}
iex> validate schema, [1, 2, 3]
:ok
iex> validate schema, [3, 2, 1, 0]
{:error, [%{
  at: 3,
  error: %{value: 0, minimum: 1}
}]}
```

`items` can also be used to give each item a specific schema.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list,
...>   items: [:integer, {:string, min_length: 5}]
%Xema{content: %Xema.Schema{
  type: :list,
  as: :list,
  items: [
    %Xema.Schema{type: :integer, as: :integer},
    %Xema.Schema{type: :string, as: :string, min_length: 5}
  ]
}}
iex> is_valid? schema, [1, "hello"]
true
iex> validate schema, [1, "five"]
{
  :error,
  [%{at: 1, error: %{value: "five", min_length: 5}}]
}
# It’s okay to not provide all of the items:
iex> validate schema, [1]
:ok
# And, by default, it’s also okay to add additional items to end:
iex> validate schema, [1, "hello", "foo"]
:ok
```

#### <a name="additional_items"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list,
...>   items: [:integer, {:string, min_length: 5}],
...>   additional_items: false
%Xema{content: %Xema.Schema{
  type: :list,
  as: :list,
  items: [
    %Xema.Schema{type: :integer, as: :integer},
    %Xema.Schema{type: :string, as: :string, min_length: 5}
  ],
  additional_items: false
}}
# It’s okay to not provide all of the items:
iex> validate schema, [1]
:ok
# But, since additionalItems is false, we can’t provide extra items:
iex> validate schema, [1, "hello", "foo"]
{:error, [%{additional_items: false, at: 2}]}
iex> validate schema, [1, "hello", "foo", "bar"]
{:error, [
  %{additional_items: false, at: 2},
  %{additional_items: false, at: 3}
]}
```

The keyword can also contain a schema to specify the type of additional items.
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list,
...>   items: [:integer, {:string, min_length: 3}],
...>   additional_items: :integer
%Xema{content: %Xema.Schema{
  type: :list,
  as: :list,
  items: [
    %Xema.Schema{type: :integer, as: :integer},
    %Xema.Schema{type: :string, as: :string, min_length: 3}
  ],
  additional_items: %Xema.Schema{type: :integer, as: :integer}
}}
iex> is_valid? schema, [1, "two", 3, 4]
true
iex> validate schema, [1, "two", 3, "four"]
{:error, [%{
  at: 3,
  error: %{type: :integer, value: "four"}
}]}
```

#### <a name="list_length"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list, min_items: 2, max_items: 3
%Xema{content: %Xema.Schema{min_items: 2, max_items: 3, type: :list, as: :list}}
iex> validate schema, [1]
{:error, %{value: [1], min_items: 2}}
iex> validate schema, [1, 2]
:ok
iex> validate schema, [1, 2, 3]
:ok
iex> validate schema, [1, 2, 3, 4]
{:error, %{value: [1, 2, 3, 4], max_items: 3}}
```

#### <a name="unique"></a> Uniqueness

A schema can ensure that each of the items in an array is unique.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :list, unique_items: true
%Xema{content: %Xema.Schema{type: :list, as: :list, unique_items: true}}
iex> is_valid? schema, [1, 2, 3]
true
iex> validate schema, [1, 2, 3, 2, 1]
{:error, %{value: [1, 2, 3, 2, 1], unique_items: true}}
```

### <a name="map"></a> 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”.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map
%Xema{content: %Xema.Schema{type: :map, as: :map}}
iex> is_valid? schema, %{"foo" => "bar"}
true
iex> validate schema, "bar"
{:error, %{type: :map, value: "bar"}}
# Using non-strings as keys are also valid:
iex> is_valid? schema, %{foo: "bar"}
true
iex> is_valid? schema, %{1 => "bar"}
true
```

#### <a name="keys"></a> Keys

The keyword `keys` can restrict the keys to atoms or strings.

Atoms as keys:
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map, keys: :atoms
%Xema{content: %Xema.Schema{type: :map, as: :map, keys: :atoms}}
iex> is_valid? schema, %{"foo" => "bar"}
false
iex> is_valid? schema, %{foo: "bar"}
true
iex> is_valid? schema, %{1 => "bar"}
false
```

Strings as keys:
```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map, keys: :strings
%Xema{content: %Xema.Schema{type: :map, as: :map, keys: :strings}}
iex> is_valid? schema, %{"foo" => "bar"}
true
iex> is_valid? schema, %{foo: "bar"}
false
iex> is_valid? schema, %{1 => "bar"}
false
```

#### <a name="properties"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...>   properties: %{
...>     a: :integer,
...>     b: {:string, min_length: 5}
...>   }
%Xema{content: %Xema.Schema{
  type: :map,
  as: :map,
  properties: %{
    a: %Xema.Schema{type: :integer, as: :integer},
    b: %Xema.Schema{type: :string, as: :string, min_length: 5}
  }
}}
iex> is_valid? schema, %{a: 5, b: "hello"}
true
iex> validate schema, %{a: 5, b: "ups"}
{:error, %{
  b: %{
    value: "ups",
    min_length: 5
  }
}}
# Additinonal properties are allowed by default:
iex> is_valid? schema, %{a: 5, b: "hello", add: :prop}
true
```

#### <a name="required_properties"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map, properties: %{foo: :string}, required: [:foo]
%Xema{
  content: %Xema.Schema{
    type: :map,
    as: :map,
    properties: %{
      foo: %Xema.Schema{type: :string, as: :string}
    },
    required: MapSet.new([:foo])
  }
}
iex> validate schema, %{foo: "bar"}
:ok
iex> validate schema, %{bar: "foo"}
{:error, %{foo: :required}}
```

#### <a name="additional_properties"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...>   properties: %{foo: :string},
...>   required: [:foo],
...>   additional_properties: false
%Xema{
  content: %Xema.Schema{
    type: :map,
    as: :map,
    properties: %{foo: %Xema.Schema{type: :string, as: :string}},
    required: MapSet.new([:foo]),
    additional_properties: false
  }
}
iex> validate schema, %{foo: "bar"}
:ok
iex> validate schema, %{foo: "bar", bar: "foo"}
{:error, %{
  bar: %{additional_properties: false}
}}
```

`additional_properties` can also contain a schema to specify the type of
additional properites.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...>   properties: %{foo: :string},
...>   additional_properties: :integer
%Xema{
  content: %Xema.Schema{
    type: :map,
    as: :map,
    properties: %{foo: %Xema.Schema{type: :string, as: :string}},
    additional_properties: %Xema.Schema{type: :integer, as: :integer}
  }
}
iex> is_valid? schema, %{foo: "foo", add: 1}
true
iex> validate schema, %{foo: "foo", add: "one"}
{:error, %{
  add: %{type: :integer, value: "one"}
}}
```

#### <a name="pattern_properties"></a> Pattern Properties

The keyword `pattern_properties` defined additional properties by regular
expressions.

```Eixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...> additional_properties: false,
...> pattern_properties: %{
...>   ~r/^s_/ => :string,
...>   ~r/^i_/ => :integer
...> }
%Xema{content: %Xema.Schema{
  type: :map,
  as: :map,
  additional_properties: false,
  pattern_properties: %{
    ~r/^s_/ => %Xema.Schema{type: :string, as: :string},
    ~r/^i_/ => %Xema.Schema{type: :integer, as: :integer}
  }
}}
iex> is_valid? schema, %{"s_0" => "foo", "i_1" => 6}
true
iex> is_valid? schema, %{s_0: "foo", i_1: 6}
true
iex> validate schema, %{s_0: "foo", f_1: 6.6}
{:error, %{
  f_1: %{additional_properties: false}
}}
```

#### <a name="map_size"></a> Size

The number of properties on an object can be restricted using the
`min_properties` and `max_properties` keywords.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...>   min_properties: 2,
...>   max_properties: 3
%Xema{content: %Xema.Schema{
  type: :map,
  as: :map,
  min_properties: 2,
  max_properties: 3
}}
iex> is_valid? schema, %{a: 1, b: 2}
true
iex> validate schema, %{}
{:error, %{min_properties: 2}}
iex> validate schema, %{a: 1, b: 2, c: 3, d: 4}
{:error, %{max_properties: 3}}
```

#### <a name="dependencies"></a> Dependencies

The `dependencies` keyword allows the schema of the object to change based on
the presence of certain special properties.

```Elixir
iex> import Xema, only: [is_valid?: 2, validate: 2]
Xema
iex> schema = Xema.new :map,
...>   properties: %{
...>     a: :number,
...>     b: :number,
...>     c: :number
...>   },
...>   dependencies: %{
...>     b: [:c]
...>   }
%Xema{content: %Xema.Schema{
  type: :map,
  as: :map,
  properties: %{
    a: %Xema.Schema{type: :number, as: :number},
    b: %Xema.Schema{type: :number, as: :number},
    c: %Xema.Schema{type: :number, as: :number}
  },
  dependencies: %{b: [:c]}
}}
iex> is_valid? schema, %{a: 5}
true
iex> is_valid? schema, %{c: 9}
true
iex> is_valid? schema, %{b: 1}
false
iex> is_valid? schema, %{b: 1, c: 7}
true
```

### <a name="enum"></a> 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.

```Elixir
iex> import Xema, only: [is_valid?: 2]
Xema
iex> schema = Xema.new :any, enum: [1, "foo", :bar]
%Xema{content: %Xema.Schema{enum: [1, "foo", :bar], type: :any, as: :any}}
iex> is_valid? schema, :bar
true
iex> is_valid? schema, 42
false
```

## References

The home of JSON Schema: http://json-schema.org/

Specification:

* [JSON Schema core](http://json-schema.org/latest/json-schema-core.html)
defines the basic foundation of JSON Schema
* [JSON Schema Validation](http://json-schema.org/latest/json-schema-validation.html)
defines the validation keywords of JSON Schema


[Understanding JSON Schema](https://spacetelescope.github.io/understanding-json-schema/index.html)
a great tutorial for JSON Schema authors and a template for the description of
Xema.