README.md

# Is

Fast, extensible and easy to use data structure validation for [elixir](https://github.com/elixir-lang/elixir) with nested structures support.

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `is` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:is, "~> 1.0.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/is](https://hexdocs.pm/is).

## Example

```elixir
iex> data = Enum.map(1..2, &(%{
...>   a: 1,
...>   b: "b",
...>   c: {"a", "b", false},
...>   d: [[1, 2, "3"], [4, false, 6]],
...>   e: -1,
...>   f: "1234567891011",
...>   index: &1 - 10,
...> }))
iex> schema = [list: [map: %{
...>   a: :binary,
...>   b: :boolean,
...>   c: [list: [or: [:binary, :boolean]]],
...>   d: [list: [list: :integer]],
...>   e: [and: [:optional, :binary]],
...>   index: [and: [:integer, in_range: [min: 0]]],
...> }]]
iex> Is.validate(data, schema)
[
  {:error, [0, :a], "must be a binary"},
  {:error, [0, :b], "must be a boolean"},
  {:error, [0, :c], "must be a list"},
  {:error, [0, :d, 0, 2], "must be an integer"},
  {:error, [0, :d, 1, 1], "must be an integer"},
  {:error, [0, :e], "must be a binary"},
  {:error, [0, :index], "must at least be 0"},
  {:error, [1, :a], "must be a binary"},
  {:error, [1, :b], "must be a boolean"},
  {:error, [1, :c], "must be a list"},
  {:error, [1, :d, 0, 2], "must be an integer"},
  {:error, [1, :d, 1, 1], "must be an integer"},
  {:error, [1, :e], "must be a binary"},
  {:error, [1, :index], "must at least be 0"},
]
```

Validations
-----------

### Basic types

#### :atom

Test if data is an atom or not

```elixir
iex> Is.validate(:value, :atom)
[]

iex> Is.validate(:value, atom: true)
[]

iex> Is.validate(1, :atom)
[{:error, [], "must be a atom"}]

iex> Is.validate(:value, atom: false)
[{:error, [], "must not be a atom"}]
```

#### :binary

Test if data is a binary or not

```elixir
iex> Is.validate("value", :binary)
[]

iex> Is.validate("value", binary: true)
[]

iex> Is.validate(1, :binary)
[{:error, [], "must be a binary"}]

iex> Is.validate("value", binary: false)
[{:error, [], "must not be a binary"}]
```

#### :boolean

Test if data is a boolean or not

```elixir
iex> Is.validate(true, :boolean)
[]

iex> Is.validate(true, boolean: true)
[]

iex> Is.validate(1, :boolean)
[{:error, [], "must be a boolean"}]

iex> Is.validate(true, boolean: false)
[{:error, [], "must not be a boolean"}]
```

#### :equals

Test if data equals given value

```elixir
iex> Is.validate(true, equals: true)
[]

iex> Is.validate("str", equals: "str")
[]

iex> Is.validate(1, equals: true)
[{:error, [], "must equals true"}]
```

#### :fn

Test if data equals given value

```elixir
iex> Is.validate(1, fn: &is_number/1)
[]

iex> Is.validate(true, fn: &is_number/1)
[{:error, [], "must satisfies &:erlang.is_number/1"}]

iex> starts_with? = fn(value, prefix) ->
...>   if String.starts_with?(value, prefix) do
...>     :ok
...>   else
...>     {:error, "must start with #{inspect prefix}"}
...>   end
...> end
iex> Is.validate("https://elixir-lang.org", fn: [starts_with?, "http"])
[]
iex> Is.validate("elixir-lang.org", fn: [starts_with?, "http"])
[{:error, [], "must start with \"http\""}]

iex> Is.validate(12, fn: {1, 2})
[{:error, [], "fn: options are invalid"}]
```