README.md

# Exchema

Exchema is a library to define, validate and coerce data. It allows
you to check the type for a given value at runtime (it is not static
type checking).

It uses the idea of **refinement types**, in which we have a global type
(which all values belong) and can refine that type with the use of
**predicates**.

## Types

A type can be:

- the global type `:any`
- a type reference such as `Exchema.Types.String`
- a type refinement such as `{:ref, :any, length: 1}`
- a type application (for parametric types) such as `{Exchema.Types.List, Exchema.Types.String}`

## Checking types

Exchema ships with some predefined types that you can check using
`Exchema.is?/2`

```elixir
iex> Exchema.is?("1234", Exchema.Types.String)
true

iex> Exchema.is?(1234, Exchema.Types.String)
false

iex> Exchema.is?(1234, Exchema.Types.Integer)
true
```

There is also the global type `:any`

```elixir
iex> Exchema.is?("1234", :any)
true

iex> Exchema.is?(1234, :any)
true
```

## Parametric types

A type can be specialized, e.g. lists can have an inner type specified, so
`{Exchema.Types.List, Exchema.Types.Integer}` represents a list of integers.

In the case of list, you can just use and not specify it directly, so
`Exchema.Types.List` is a list of elements of any type, or
`{Exchema.Types.List, :any}`.

Some types can have multiple parameters, e.g. a map.
`{Exchema.Types.Map, {Exchema.Types.String, Exchema.Types.Integer}}` represents
a map from strings to integer.

Types with 0 params can be represented just by the module name.
Types with 1 param can be represented by a tuple `{type, argument}`
Types with N params can be represented by a tuple `{type, arguments}` where
arguments is a tuple with N elements.

## Type refinement

This is the core of our system, it allows to create more specialized types starting
from the global type `:any`. In fact, all the types that ships with Exchema are
just refinements from other types. (You will see it more in defining your own types)

A refined type is a 3-tuple with the `:ref` keyword, the source type and a list of
2-tuples containing the predicate reference and the predicate argument

A predicate is basically a reference to a function and one argument.

```elixir
#           source type==||       ||==predicate reference
#                        ||       ||
#     ref keyword==||    ||       ||  ||==predicate argument
#                  \/    \/       \/  \/
iex> Exchema.is?({:ref, :any, length: 1 }, [])
false

iex> Exchema.is?({:ref, :any, length: 1}, ["element"])
true
```

## Predicate

A predicate in the end is just a function with 2 arguments, the first being the value
to check and the second is an arbitrary argument. It should return either
`true` or `:ok` to represent a valid value and `false`, `{:error, error}` or
`[{:error, error}]` to represent an invalid value.

## Predicate References

Here `:length` is the reference to a function (which comes from a predicate library).

By default, Exchema ships with a predicate library which you can check at `Exchema.Predicates`
and that module exposes some functions, e.g. `length/2`.

You can also reference a function directly in the form of `{module, function_name}`, so actually
`:length` is the same as `{Exchema.Predicates, :length}` (expect if you override the default lib).

## Defining your own type

In the type refinement section you "already defined" a new type, but you didn't give it a name.

When refining `:any` with `length: 1` predicate you created a new type.

To be able to reference it by name, you need to define a module.

A type reference is just a module that defines a function `___type__/1`.

That function will receive as the argument a tuple with the arguments passed when referencing
the type.

If you want to define a type without params, you can do it by defining a module like

```elixir
defmodule MyType do
  # we can ignore the argument because it is not a parametric type
  def __type__(_) do
    {:ref, :any, length: 1}
  end
end
```

What happens is that when the type is trying to check against a type `MyType`, it will
call `MyType.__type__({})` and see the response. This is the type resolving procedure.

Notice that an empty tuple is passed (this is because you referenced it as `MyType`),
with that being said `Exchema.is?(val, MyType)` is the same as `Exchema.is?(val, {MyType, {}})`.

If you want to define a parametric type you can just accept a tuple with size 1 (or more if you want)

```elixir
defmodule MyType do
  # Here we are defining a type that can receive 1 or 0 params.
  # If no params are given, it is a type with length 1, otherwise it has an arbitrary length.
  def __type__({}), do: __type__({0})
  def __type__({length}) do
    {:ref, :any, length: length}
  end
end
```

Now you can use that type as `{MyType, 10}` or `{MyType, {10}}`, which are equivalent.

## Struct Types

A lot of data types in elixir are just structs. That's why we have an special type (and predicates)
to treat structs.

Let's say we want to represent a geographic location, a coordinate. We can use the type `Exchema.Types.Struct`.

This type receives two arguments, a module (the struct module) and a list of field types, which is a list
of tuples containing the key and the expected type, e.g. `{:long, Exchema.Types.Float}`. But you can use
elixir syntax sugar to make it prettier, like the example below.

```elixir
defmodule Coord do
  defstruct [:long, :lat]
  
  def __type__(_) do
    {Exchema.Types.Struct, {__MODULE__, [
      long: Exchema.Types.Number,
      lat: Exchema.Types.Number
    ]}}
  end
end

iex> Exchema.is?(%{}, Coord)
false

iex> Exchema.is?(%Coord{long: "", lat: ""}, Coord)
false

iex> Exchema.is?(%Coord{long: 10.0, lat: 10.0}, Coord)
true
```

## Exchema.Struct

However, defining struct types can be really cumbersome and thats why we have
the `Exchema.Struct` module which you can use as

```elixir
defmodule Coord do
  use Exchema.Struct, fields: [
    long: Exchema.Types.Number,
    lat: Exchema.Types.Number
  ]
end
```

And you can alias `Exchema.Types` to have smaller type definitions.

```elixir
defmodule Coord do
  alias Exchema.Types, as: T

  use Exchema.Struct, fields: [
    long: T.Number,
    lat: T.Number
  ]
end
```

## Defining your own predicate

Defining a predicate is as simple as defining a function.

Let's say we want to use `uuid` lib to validate wheter or not a string
is a UUID.

```elixir 
defmodule MyPredicates do
  def uuid(value, _) when is_binary(value) do
    case UUID.info(value) do
      :ok ->
        :ok
      _ ->
        {:error, :not_a_valid_uuid}
    end
  end
  def uuid(_, _), do: {:error, :not_a_valid_uuid}
end
```

Now we can use that in our types

```elixir
defmodule MyUUID do
  def __type__(_) do
    {:ref, Exchema.Types.String, [
      {{MyPredicates, :uuid}, true}
    ]}
  end
end
```

## Configuring your predicate library

You can configure Exchema to include your predicate library by adding
```elixir
config :exchema,
  predicates: [MyPredicates]
```

Now you can use the function name directly, so you can rewrite your type to
be:

```elixir
defmodule MyUUID do
  def __type__(_) do
    {:ref, Exchema.Types.String, uuid: true}
  end
end
```

And then you can check the uuid

```elixir
iex> Exchema.is?("randomstring", MyUUID)
false

iex> Exchema.is?("f4fd18af-1e8d-4262-a655-c1fa83ae9162", MyUUID)
true
```

## Coercion

This should probably move to another library, but for now it is bundled here.

`Exchema.Coercion` can receive some input and coerce to a specific type.

```elixir
iex> Exchema.Coercion.coerce("2018-01-01", Exchema.Types.Date)
~D[2018-01-01]

defmodule MyStruct do
  use Exchema.Struct, fields: [
    foo: Exchema.Types.Integer,
    bar: Exchema.Types.Date
  ]
end

iex> Exchema.Coercion.coerce(%{"foo" => 1, "bar" => "2018-01-01"}, MyStruct)
%MyStruct{
  foo: 1,
  bar: ~D[2018-01-01]
}

iex> Exchema.Coercion.coerce(["1", 2, 3.0], {Exchema.Types.List, Exchema.Types.Integer})
[1,2,3]
```

## Installation

Add `exchema` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:exchema, "~> 0.2.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/exchema](https://hexdocs.pm/exchema).