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)
[![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.1"}]
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)
  * [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
Xema
iex> schema = xema :any
%Xema{type: %Xema.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
Xema
iex> schema = xema :nil
%Xema{type: %Xema.Nil{}}
iex> validate schema, nil
:ok
iex> validate schema, 0
{:error, %{reason: :wrong_type, type: :nil}}
```

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

The boolean type matches only `true` and `false`.
```Elixir
iex> import Xema
Xema
iex> schema = xema :boolean
%Xema{type: %Xema.Boolean{}}
iex> validate schema, true
:ok
iex> is_valid? schema, false
true
iex> validate schema, 0
{:error, %{reason: :wrong_type, type: :boolean}}
iex> is_valid? schema, nil
false
```

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

The string type is used for strings.

```elixir
iex> import Xema
Xema
iex> schema = xema :string
%Xema{type: %Xema.String{}}
iex> validate schema, "José"
:ok
iex> validate schema, 42
{:error, %{reason: :wrong_type, type: :string}}
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
Xema
iex> schema = xema :string, min_length: 2, max_length: 3
%Xema{type: %Xema.String{min_length: 2, max_length: 3}}
iex> validate schema, "a"
{:error, %{reason: :too_short, min_length: 2}}
iex> validate schema, "ab"
:ok
iex> validate schema, "abc"
:ok
iex> validate schema, "abcd"
{:error, %{reason: :too_long, 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
Xema
iex> schema = xema :string, pattern: ~r/[0-9]-[A-B]+/
%Xema{type: %Xema.String{pattern: ~r/[0-9]-[A-B]+/}}
iex> validate schema, "1-AB"
:ok
iex> validate schema, "foo"
{:error, %{reason: :no_match, 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
Xema
iex> schema = xema :number
%Xema{type: %Xema.Number{}}
iex> validate schema, 42
:ok
iex> validate schema, 21.5
:ok
iex> validate schema, "foo"
{:error, %{reason: :wrong_type, type: :number}}
```

The `integer` type is used for integral numbers.
```Elixir
iex> import Xema
Xema
iex> schema = xema :integer
%Xema{type: %Xema.Integer{}}
iex> validate schema, 42
:ok
iex> validate schema, 21.5
{:error, %{reason: :wrong_type, type: :integer}}
```

The `float` type is used for floating point numbers.
```Elixir
iex> import Xema
Xema
iex> schema = xema :float
%Xema{type: %Xema.Float{}}
iex> validate schema, 42
{:error, %{reason: :wrong_type, type: :float}}
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
Xema
iex> schema = xema :number, multiple_of: 2
%Xema{type: %Xema.Number{multiple_of: 2}}
iex> validate schema, 8
:ok
iex> validate schema, 7
{:error, %{reason: :not_multiple, 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
Xema
iex> schema = xema :float, minimum: 1.2, maximum: 1.4, exclusive_maximum: true
%Xema{type: %Xema.Float{minimum: 1.2, maximum: 1.4, exclusive_maximum: true}}
iex> validate schema, 1.1
{:error, %{reason: :too_small, minimum: 1.2}}
iex> validate schema, 1.2
:ok
iex> is_valid? schema, 1.3
true
iex> validate schema, 1.4
{:error, %{reason: :too_big, maximum: 1.4, exclusive_maximum: true}}
iex> validate schema, 1.5
{:error, %{reason: :too_big, maximum: 1.4}}
```

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

```Elixir
iex> import Xema
Xema
iex> schema = xema :list
%Xema{type: %Xema.List{}}
iex> is_valid? schema, [1, "two", 3.0]
true
iex> validate schema, 9
{:error, %{reason: :wrong_type, type: :list}}
```

#### <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
Xema
iex> schema = xema :list, items: :string
%Xema{type: %Xema.List{items: %Xema.String{}}}
iex> is_valid? schema, ["a", "b", "abc"]
true
iex> validate schema, ["a", 1]
{
  :error,
  %{reason: :invalid_item, at: 1, error: %{reason: :wrong_type, type: :string}}
}
```

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

```Elixir
iex> import Xema
Xema
iex> schema = xema :list, items: {:integer, minimum: 1, maximum: 10}
%Xema{type: %Xema.List{items: %Xema.Integer{minimum: 1, maximum: 10}}}
iex> validate schema, [1, 2, 3]
:ok
iex> validate schema, [3, 2, 1, 0]
{
  :error,
  %{reason: :invalid_item, at: 3, error: %{reason: :too_small, minimum: 1}}
}
```

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

```Elixir
iex> import Xema
Xema
iex> schema = xema :list,
...>   items: [:integer, {:string, min_length: 5}]
%Xema{type: %Xema.List{
  items: [%Xema.Integer{}, %Xema.String{min_length: 5}]
}}
iex> is_valid? schema, [1, "hello"]
true
iex> validate schema, [1, "five"]
{
  :error,
  %{reason: :invalid_item, at: 1, error: %{reason: :too_short, 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
Xema
iex> schema = xema :list,
...>   items: [:integer, {:string, min_length: 5}],
...>   additional_items: false
%Xema{type: %Xema.List{
  items: [%Xema.Integer{}, %Xema.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, %{reason: :additional_item, at: 2}}
iex> validate schema, [1, "hello", "foo", "bar"]
{:error, %{reason: :additional_item, at: 2}}
```

#### <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
Xema
iex> schema = xema :list, min_items: 2, max_items: 3
%Xema{type: %Xema.List{min_items: 2, max_items: 3}}
iex> validate schema, [1]
{:error, %{reason: :too_less_items, min_items: 2}}
iex> validate schema, [1, 2]
:ok
iex> validate schema, [1, 2, 3]
:ok
iex> validate schema, [1, 2, 3, 4]
{:error, %{reason: :too_many_items, 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
Xema
iex> schema = xema :list, unique_items: true
%Xema{type: %Xema.List{unique_items: true}}
iex> is_valid? schema, [1, 2, 3]
true
iex> validate schema, [1, 2, 3, 2, 1]
{:error, %{reason: :not_unique}}
```

### <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
Xema
iex> schema = xema :map
%Xema{type: %Xema.Map{}}
iex> is_valid? schema, %{"foo" => "bar"}
true
iex> validate schema, "bar"
{:error, %{reason: :wrong_type, type: :map}}
# 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
Xema
iex> schema = xema :map, keys: :atoms
%Xema{type: %Xema.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
Xema
iex> schema = xema :map, keys: :strings
%Xema{type: %Xema.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
Xema
iex> schema = xema :map,
...>   properties: %{
...>     a: :integer,
...>     b: {:string, min_length: 5}
...>   }
%Xema{type: %Xema.Map{
  properties: %{
    a: %Xema.Integer{},
    b: %Xema.String{min_length: 5}
  }
}}
iex> is_valid? schema, %{a: 5, b: "hello"}
true
iex> validate schema, %{a: 5, b: "ups"}
{:error, %{
  reason: :invalid_property,
  property: :b,
  error: %{
    reason: :too_short,
    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
Xema
iex> schema = xema :map, properties: %{foo: :string}, required: [:foo]
%Xema{
  type: %Xema.Map{
    properties: %{foo: %Xema.String{}},
    required: MapSet.new([:foo])
  }
}
iex> validate schema, %{foo: "bar"}
:ok
iex> validate schema, %{bar: "foo"}
{:error, %{reason: :missing_properties, missing: [:foo], required: [:foo]}}
```

#### <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 object. If
`additional_properties` is a boolean and set to false, no additional properties
will be allowed.

```Elixir
iex> import Xema
Xema
iex> schema = xema :map,
...>   properties: %{foo: :string},
...>   required: [:foo],
...>   additional_properties: false
%Xema{
  type: %Xema.Map{
    properties: %{foo: %Xema.String{}},
    required: MapSet.new([:foo]),
    additional_properties: false
  }
}
iex> validate schema, %{foo: "bar"}
:ok
iex> validate schema, %{foo: "bar", bar: "foo"}
{:error, %{
  reason: :no_additional_properties_allowed,
  additional_properties: [:bar]}
}
```

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

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

```Eixir
iex> import Xema
Xema
iex> schema = xema :map,
...> additional_properties: false,
...> pattern_properties: %{
...>   ~r/^s_/ => :string,
...>   ~r/^i_/ => :integer
...> }
%Xema{type: %Xema.Map{
  additional_properties: false,
  pattern_properties: %{
    ~r/^s_/ => %Xema.String{},
    ~r/^i_/ => %Xema.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, %{
  reason: :no_additional_properties_allowed,
  additional_properties: [:f_1]
}}
```

#### <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
Xema
iex> schema = xema :map,
...>   min_properties: 2,
...>   max_properties: 3
%Xema{type: %Xema.Map{
  min_properties: 2,
  max_properties: 3
}}
iex> is_valid? schema, %{a: 1, b: 2}
true
iex> validate schema, %{}
{:error, %{reason: :too_less_properties, min_properties: 2}}
iex> validate schema, %{a: 1, b: 2, c: 3, d: 4}
{:error, %{reason: :too_many_properties, 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
Xema
iex> schema = xema :map,
...>   properties: %{
...>     a: :number,
...>     b: :number,
...>     c: :number
...>   },
...>   dependencies: %{
...>     b: [:c]
...>   }
%Xema{type: %Xema.Map{
  properties: %{a: %Xema.Number{}, b: %Xema.Number{}, c: %Xema.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
Xema
iex> schema = xema :any, enum: [1, "foo", :bar]
%Xema{type: %Xema.Any{enum: [1, "foo", :bar]}}
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.