# `BB.Parameter.Type`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/parameter/type.ex#L5)

Validation for parameter type definitions in the DSL.

Parameters can have simple types (`:float`, `:integer`, etc.) or unit types
like `{:unit, :meter}`.

Numeric parameters - `:float`, `:integer` and unit types - can also declare
`min`/`max` bounds. `option_type/3` folds those bounds into the
`Spark.Options` type generated for the parameter so that they are enforced
wherever a value is validated, and `describe/1` recovers them from a
generated type for display.

# `bound`

```elixir
@type bound() :: number() | Localize.Unit.t() | nil
```

# `t`

```elixir
@type t() :: :float | :integer | :boolean | :string | :atom | {:unit, atom()}
```

# `describe`

```elixir
@spec describe(Spark.Options.type() | nil) ::
  {t() | Spark.Options.type() | nil, bound(), bound()}
```

Recovers the declared parameter type and its bounds from a generated
`Spark.Options` type.

Types which don't carry bounds - including the hand-written schemas of
components which `use BB.Parameter` - are returned unchanged with `nil`
bounds.

## Examples

    iex> BB.Parameter.Type.describe(:float)
    {:float, nil, nil}

    iex> {:ok, type} = BB.Parameter.Type.option_type(:integer, 0, 127)
    iex> BB.Parameter.Type.describe(type)
    {:integer, 0, 127}

    iex> {:ok, type} = BB.Parameter.Type.option_type({:unit, :meter}, nil, Localize.Unit.new!(1, "meter"))
    iex> BB.Parameter.Type.describe(type)
    {{:unit, :meter}, nil, Localize.Unit.new!(1, "meter")}

# `option_type`

```elixir
@spec option_type(t(), bound(), bound()) ::
  {:ok, Spark.Options.type()} | {:error, String.t()}
```

Builds the `Spark.Options` type for a parameter of `type`, bounded by `min`
and `max`.

Either bound may be `nil`. Numeric bounds are plain numbers, bounds on a unit
type are `Localize.Unit` values compatible with the parameter's unit.

## Examples

An unbounded type is used as-is:

    iex> BB.Parameter.Type.option_type(:float, nil, nil)
    {:ok, :float}

Bounds wrap the type in a custom validator:

    iex> BB.Parameter.Type.option_type(:integer, 0, 127)
    {:ok, {:custom, BB.Parameter.Type, :validate_bounds, [[type: :integer, min: 0, max: 127]]}}

Only one of the two is needed:

    iex> BB.Parameter.Type.option_type(:float, 0.0, nil)
    {:ok, {:custom, BB.Parameter.Type, :validate_bounds, [[type: :float, min: 0.0, max: nil]]}}

Bounds on a unit type become unit constraints:

    iex> BB.Parameter.Type.option_type({:unit, :meter}, nil, Localize.Unit.new!(1, "meter"))
    {:ok, {:custom, BB.Unit.Option, :validate, [[compatible: :meter, max: Localize.Unit.new!(1, "meter")]]}}

Non-numeric types cannot be bounded:

    iex> BB.Parameter.Type.option_type(:string, 0, nil)
    {:error, "`min` and `max` are only supported for numeric parameter types (:float, :integer or {:unit, unit_type}), got: :string"}

# `validate`

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

Validates a parameter type specification.

Returns `{:ok, type}` for valid types or `{:error, message}` for invalid ones.

## Valid Types

- Simple types: `:float`, `:integer`, `:boolean`, `:string`, `:atom`
- Unit types: `{:unit, unit_type}` where `unit_type` is a valid CLDR unit

## Examples

    iex> BB.Parameter.Type.validate(:float)
    {:ok, :float}

    iex> BB.Parameter.Type.validate({:unit, :meter})
    {:ok, {:unit, :meter}}

    iex> BB.Parameter.Type.validate(:invalid)
    {:error, "Expected one of [:float, :integer, :boolean, :string, :atom] or {:unit, unit_type}, got: :invalid"}

# `validate_bounds`

```elixir
@spec validate_bounds(
  term(),
  keyword()
) :: {:ok, number()} | {:error, String.t()}
```

Validates a value against a bounded numeric parameter's type and bounds.

This is the validator used by the type `option_type/3` builds for a bounded
`:float` or `:integer` parameter.

## Examples

    iex> BB.Parameter.Type.validate_bounds(0.5, type: :float, min: 0.0, max: 1.0)
    {:ok, 0.5}

    iex> BB.Parameter.Type.validate_bounds(2.0, type: :float, min: 0.0, max: 1.0)
    {:error, "expected value to be at most 1.0, got: 2.0"}

    iex> BB.Parameter.Type.validate_bounds(-1.0, type: :float, min: 0.0, max: nil)
    {:error, "expected value to be at least 0.0, got: -1.0"}

    iex> BB.Parameter.Type.validate_bounds("nope", type: :float, min: 0.0, max: 1.0)
    {:error, "expected float, got: \"nope\""}

---

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