# `Localize.Inputs.Number.Validator`
[🔗](https://github.com/elixir-localize/localize_number_inputs/blob/v0.1.1/lib/localize/inputs/number/validator.ex#L1)

Server-side validation for parsed number values.

Pure Elixir, no Ecto dependency. The Ecto changeset bridge is
in `Localize.Inputs.Number.Changeset`.

# `validate_number`

```elixir
@spec validate_number(term(), Keyword.t()) ::
  :ok | {:error, Localize.Inputs.ValidationError.t()}
```

Validates a parsed number against bounds, precision, and
required-ness.

### Arguments

* `value` is a `Decimal`, integer, or `nil`.

* `options` is a keyword list of options.

### Options

* `:required` — when `true`, `nil` is rejected.

* `:min` — minimum allowed value (any numeric form the parser
  accepts).

* `:max` — maximum allowed value.

* `:decimals` — maximum number of fractional digits.

### Returns

* `:ok` when every check passes.

* `{:error, %Localize.Inputs.ValidationError{errors: [{atom(),
  String.t()}]}}` with one entry per failing check, in the
  order `:required`, `:min`, `:max`, `:decimals`.
  `Localize.Inputs.Number.Changeset.validate_number/3` unpacks the
  entries into per-field changeset errors.

### Examples

    iex> Localize.Inputs.Number.Validator.validate_number(Decimal.new("5"), min: 1, max: 10)
    :ok

    iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
    ...>   Localize.Inputs.Number.Validator.validate_number(Decimal.new("15"), max: 10)
    iex> errors
    [{:max, "must be at most 10"}]

    iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
    ...>   Localize.Inputs.Number.Validator.validate_number(nil, required: true)
    iex> errors
    [{:required, "is required"}]

# `validate_unit`

```elixir
@spec validate_unit(term(), Keyword.t()) ::
  :ok | {:error, Localize.Inputs.ValidationError.t()}
```

Validates a unit-of-measure form submission.

Accepts the `%{"amount" => ..., "unit" => ...}` map shape that
`Localize.Inputs.Number.Components.unit_input/1` submits. Checks that
the amount passes `validate_number/2` and that the unit is a
known unit in the given category.

### Arguments

* `value` — a `%{"amount", "unit"}` map (string or atom keys),
  a bare numeric value, `nil`, or `""`.

* `options` is a keyword list of options.

### Options

* `:category` — the unit category as a string (e.g. `"length"`).
  **Required.** The submitted unit is checked against
  `Localize.Inputs.Number.Unit.all_unit_names/2`.

* `:required` — when `true`, `nil` amount is rejected.

* `:min`, `:max`, `:decimals` — forwarded to `validate_number/2`.

### Returns

* `:ok` on success.

* `{:error, ValidationError.t()}` on any failure — combined
  amount-validation errors plus a `{:unit, "..."}` error if
  the unit is missing or not in the category.

### Examples

    iex> Localize.Inputs.Number.Validator.validate_unit(
    ...>   %{"amount" => Decimal.new("1.75"), "unit" => "meter"},
    ...>   category: "length"
    ...> )
    :ok

    iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
    ...>   Localize.Inputs.Number.Validator.validate_unit(
    ...>     %{"amount" => Decimal.new("1.75"), "unit" => "bogon"},
    ...>     category: "length"
    ...>   )
    iex> Keyword.get(errors, :unit) =~ "bogon"
    true

    iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
    ...>   Localize.Inputs.Number.Validator.validate_unit(
    ...>     %{"amount" => Decimal.new("70"), "unit" => "kilogram"},
    ...>     category: "length"
    ...>   )
    iex> Keyword.get(errors, :unit) =~ "mass"
    true

---

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