# `Erabi.Rules`
[🔗](https://github.com/hattoriai/erabi/blob/v0.1.0/lib/erabi/rules.ex#L1)

What a picker accepts: the same rules in the browser, as people choose,
and on the server, in `Erabi.Changeset`.

    @rules Erabi.Rules.new(max: 3, create: true, max_length: 24)

    <.tags field={@form[:labels]} options={@labels} rules={@rules} />

    changeset
    |> cast(params, [:labels])
    |> Erabi.Changeset.validate_choice(:labels, @rules, options: @labels)

The rules:

  * `:required` - at least one value;
  * `:min`, `:max` - how many values, for a picker of several;
  * `:create` - whether a value that is not an option may be chosen
    (typed, as a new tag, or kept as text in a combobox); without it,
    every value must be an option;
  * `:min_length`, `:max_length` - the length of a new value, in
    characters (graphemes);
  * `:pattern` - a regular expression a new value must match, whole.
    It runs in the browser too (`RegExp` with the `u` flag), so keep to
    what both have: classes, ranges, `\d`, `\w`, `\s`, `\p{L}`,
    counts and alternatives.

A picker of several never posts the same value twice.

The browser checks the rules as people choose: it does not add a value
past `:max`, and says why it cannot create a value. The server checks
them again: the browser is a convenience, and a form can be posted by
anything.

# `error`

```elixir
@type error() :: {atom(), keyword()}
```

Why values are refused, with what the message needs:

  * `{:required, []}`
  * `{:too_few, min: n}`, `{:too_many, max: n}`
  * `{:not_allowed, value: v}` - not an option, and `:create` is off
  * `{:too_short, min_length: n, value: v}`, `{:too_long, max_length: n, value: v}`
  * `{:format, value: v}` - a new value that does not match `:pattern`
  * `{:duplicate, value: v}`

# `t`

```elixir
@type t() :: %Erabi.Rules{
  create: boolean(),
  max: pos_integer() | nil,
  max_length: pos_integer() | nil,
  min: non_neg_integer() | nil,
  min_length: non_neg_integer() | nil,
  pattern: String.t() | nil,
  required: boolean()
}
```

# `check`

```elixir
@spec check(t(), [String.t()], [String.t()] | :any) :: :ok | {:error, error()}
```

Checks the values that a picker posts (texts, see
`Erabi.Option.params/1`) against the rules: `:ok`, or the first reason
they are refused. `known` is the values of the options, or `:any` when
the options are not all known here (a search): then values are not
refused for not being options.

    iex> rules = Erabi.Rules.new(max: 2)
    iex> Erabi.Rules.check(rules, ["a", "b", "c"], ["a", "b", "c"])
    {:error, {:too_many, [max: 2]}}
    iex> Erabi.Rules.check(rules, ["x"], ["a", "b"])
    {:error, {:not_allowed, [value: "x"]}}

# `check_value`

```elixir
@spec check_value(t(), String.t()) :: :ok | {:error, error()}
```

Checks a value that is not an option, as a person creates it: `:ok`, or
why it cannot be.

    iex> Erabi.Rules.check_value(Erabi.Rules.new(create: true, max_length: 3), "four")
    {:error, {:too_long, [max_length: 3, value: "four"]}}

# `new`

```elixir
@spec new(t() | keyword() | nil) :: t()
```

Rules from a keyword list (see the moduledoc), or the rules as they are.
Raises an `ArgumentError` that says what is wrong.

    iex> Erabi.Rules.new(max: 3)
    %Erabi.Rules{max: 3}

---

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