# `Drafter.Validation`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/validation.ex#L1)

Input validation for TUI widgets.

A validator is one of:

  * a one-argument function returning `:ok` or `{:error, message}`
  * `:required` or `:email`
  * `{:min_length, min}`, `{:max_length, max}`, `{:pattern, regex_or_string}`,
    `{:range, min, max}`, `{:custom, fun}`
  * any of the above tuple forms with a trailing message string that replaces
    the default one, e.g. `{:max_length, 100, "Too long"}`; `:required` and
    `:email` take `{:required, message}` and `{:email, message}`

Every check but `:required` treats a value of the wrong type as a failure, so a
non-binary fails `:min_length` and a non-number fails `:range`.

    iex> Drafter.Validation.validate("a@b.co", [:required, :email])
    :ok

    iex> Drafter.Validation.validate("", [{:required, "Name needed"}])
    {:error, "Name needed"}

# `validation_result`

```elixir
@type validation_result() :: :ok | {:error, String.t()}
```

What every check returns: `:ok`, or `{:error, message}` with a display string.

# `validator`

```elixir
@type validator() ::
  (any() -&gt; validation_result())
  | :required
  | :email
  | {:required, String.t()}
  | {:email, String.t()}
  | {:min_length, non_neg_integer()}
  | {:min_length, non_neg_integer(), String.t()}
  | {:max_length, non_neg_integer()}
  | {:max_length, non_neg_integer(), String.t()}
  | {:pattern, Regex.t() | String.t()}
  | {:pattern, Regex.t() | String.t(), String.t()}
  | {:range, number(), number()}
  | {:range, number(), number(), String.t()}
  | {:custom, (any() -&gt; validation_result())}
```

Every form `run_validator/2` accepts.

Any other term raises `FunctionClauseError` — including `{:combined, validators}`
as returned by `combine/1`.

# `combine`

```elixir
@spec combine([validator()]) :: {:combined, [validator()]}
```

Wrap a list of validators as `{:combined, validators}`.

`run_validator/2` has no clause for this shape, so pass the list to `validate/2`
instead of combining it.

# `custom`

```elixir
@spec custom((any() -&gt; validation_result())) :: validator()
```

Wrap a one-argument function as the validator `{:custom, fun}`.

`fun` is called with the value and must return `:ok` or `{:error, message}`.

## Examples

    iex> validator = Drafter.Validation.custom(fn v -> if v > 0, do: :ok, else: {:error, "positive only"} end)
    iex> Drafter.Validation.run_validator(1, validator)
    :ok
    iex> Drafter.Validation.run_validator(-1, validator)
    {:error, "positive only"}

# `email`

```elixir
@spec email(any()) :: validation_result()
```

Fails unless `value` is a binary of the form `local@domain.tld`, with no spaces
or further `@`.

A non-binary fails. The two-argument form takes the failure message, default
`"Invalid email address"`.

## Examples

    iex> Drafter.Validation.email("a@b.co")
    :ok

    iex> Drafter.Validation.email("a@b")
    {:error, "Invalid email address"}

    iex> Drafter.Validation.email(nil, "Need an address")
    {:error, "Need an address"}

# `email`

```elixir
@spec email(any(), String.t()) :: validation_result()
```

# `max_length`

```elixir
@spec max_length(any(), non_neg_integer()) :: validation_result()
```

Fails unless `value` is a binary of at most `max` graphemes.

A non-binary fails. The three-argument form takes the failure message, default
`"Must be at most <max> characters"`.

## Examples

    iex> Drafter.Validation.max_length("ab", 3)
    :ok

    iex> Drafter.Validation.max_length("abcd", 3)
    {:error, "Must be at most 3 characters"}

    iex> Drafter.Validation.max_length("abcd", 3, "Too long")
    {:error, "Too long"}

# `max_length`

```elixir
@spec max_length(any(), non_neg_integer(), String.t()) :: validation_result()
```

# `min_length`

```elixir
@spec min_length(any(), non_neg_integer()) :: validation_result()
```

Fails unless `value` is a binary of at least `min` graphemes.

A non-binary fails. The three-argument form takes the failure message, default
`"Must be at least <min> characters"`.

## Examples

    iex> Drafter.Validation.min_length("abcde", 3)
    :ok

    iex> Drafter.Validation.min_length("ab", 3)
    {:error, "Must be at least 3 characters"}

    iex> Drafter.Validation.min_length(12, 3)
    {:error, "Must be at least 3 characters"}

# `min_length`

```elixir
@spec min_length(any(), non_neg_integer(), String.t()) :: validation_result()
```

# `pattern`

```elixir
@spec pattern(any(), Regex.t() | String.t()) :: validation_result()
```

Fails unless `regex` matches somewhere in `value`.

`regex` is a `Regex` or a source string, which is compiled and so raises
`Regex.CompileError` if malformed. Anchor it yourself to require a full match. A
non-binary `value` fails. The three-argument form takes the failure message,
default `"Invalid format"`.

## Examples

    iex> Drafter.Validation.pattern("abc123", "\\d+")
    :ok

    iex> Drafter.Validation.pattern("abc", ~r/^\d+$/)
    {:error, "Invalid format"}

    iex> Drafter.Validation.pattern("abc", ~r/^\d+$/, "Digits only")
    {:error, "Digits only"}

# `pattern`

```elixir
@spec pattern(any(), Regex.t() | String.t(), String.t()) :: validation_result()
```

# `range`

```elixir
@spec range(any(), number(), number()) :: validation_result()
```

Fails unless `value` is a number in `min..max` inclusive.

A non-number fails. The four-argument form takes the failure message, default
`"Must be between <min> and <max>"`.

## Examples

    iex> Drafter.Validation.range(5, 1, 10)
    :ok

    iex> Drafter.Validation.range(11, 1, 10)
    {:error, "Must be between 1 and 10"}

    iex> Drafter.Validation.range("5", 1, 10)
    {:error, "Must be between 1 and 10"}

# `range`

```elixir
@spec range(any(), number(), number(), String.t()) :: validation_result()
```

# `required`

```elixir
@spec required(any()) :: validation_result()
```

Fails when `value` is `nil`, `""` or `[]`; anything else passes.

The two-argument form takes the failure message, default
`"This field is required"`. Note that `0`, `false` and `%{}` all pass.

## Examples

    iex> Drafter.Validation.required("x")
    :ok

    iex> Drafter.Validation.required(nil)
    {:error, "This field is required"}

    iex> Drafter.Validation.required([], "Pick one")
    {:error, "Pick one"}

    iex> Drafter.Validation.required(false)
    :ok

# `required`

```elixir
@spec required(any(), String.t()) :: validation_result()
```

# `run_validator`

```elixir
@spec run_validator(any(), validator()) :: validation_result()
```

Run one validator against `value`.

Returns `:ok` or `{:error, message}`. Raises `FunctionClauseError` for a term
that is not one of the validator forms listed in the module documentation.

## Examples

    iex> Drafter.Validation.run_validator("abc", {:min_length, 5})
    {:error, "Must be at least 5 characters"}

    iex> Drafter.Validation.run_validator("abc", {:min_length, 5, "Too short"})
    {:error, "Too short"}

    iex> Drafter.Validation.run_validator(7, {:range, 1, 10})
    :ok

    iex> Drafter.Validation.run_validator("x", fn v -> if v == "x", do: :ok, else: {:error, "no"} end)
    :ok

# `validate`

```elixir
@spec validate(any(), [validator()]) :: validation_result()
```

Run `validators` against `value` in order.

Returns `:ok` when all pass, or the first `{:error, message}` and stops there.
An empty list is `:ok`.

## Examples

    iex> Drafter.Validation.validate("a@b.co", [:required, :email])
    :ok

    iex> Drafter.Validation.validate("nope", [:required, :email])
    {:error, "Invalid email address"}

    iex> Drafter.Validation.validate("", [{:required, "Name needed"}])
    {:error, "Name needed"}

    iex> Drafter.Validation.validate("anything", [])
    :ok

---

*Consult [api-reference.md](api-reference.md) for complete listing*
