BB.Parameter.Type (bb v0.30.1)

Copy Markdown View Source

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.

Summary

Functions

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

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

Validates a parameter type specification.

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

Types

bound()

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

t()

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

Functions

describe(type)

@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(type, min, max)

@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(type)

@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(value, bounds)

@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\""}