# `Mold`
[🔗](https://github.com/fuelen/mold/blob/v0.1.0/lib/mold.ex#L1)

A tiny, zero-dependency parsing library for external payloads.

Mold parses JSON APIs, webhooks, HTTP params and other external input into clean Elixir terms.

See the [Cheatsheet](cheatsheet.cheatmd) for a quick reference.

## Types

A Mold type is plain Elixir data. Every type is one of three things:

| Form | Example | Meaning |
|---|---|---|
| Atom | `:string` | Built-in type with default options |
| Function | `&MyApp.parse_email/1` | Custom parse `fn value -> {:ok, v} \| {:error, r} \| :error end` |
| Tuple | `{:integer, min: 0}` | Type (atom or function) with options |

Options refine the type: `:integer` is one type, `{:integer, min: 0}` is a different,
more precise type — like saying `positive_integer`. Each combination describes
a specific set of values, not a type with separate validation rules bolted on.

Maps and lists have a shortcut syntax:

| Shortcut | Example | Expands to |
|---|---|---|
| Map | `%{name: :string}` | `{:map, fields: [name: :string]}` |
| List | `[:string]` | `{:list, type: :string}` |

Shortcuts support options via tuple: `{%{name: :string}, nilable: true}`,
`{[:string], reject_invalid: true}`. These can be nested: `[%{name: :string}]` is a list of maps.

Shortcuts don't support field options like `source:` or `optional:`.
Use the full `{:map, fields: [...]}` syntax when you need those.

See the type definitions below for details and examples on each built-in type.

## Shared options

All types accept the following options:

  * `:nilable` – allows `nil` as a valid value.
  * `:default` – substitutes `value` when nil. Accepts a static value,
    a zero-arity function, or an MFA tuple `{mod, fun, args}` for lazy evaluation.
    Implies nilable. Note: a 3-tuple `{atom, atom, list}` is always treated as MFA.
    To use such a tuple as a static default, wrap it: `default: fn -> {Mod, :fun, []} end`.
  * `:in` – validates that the parsed value is a member of the given `t:Enumerable.t/0`
    (list, range, MapSet, etc.).
  * `:transform` – a function applied to the parsed value before validation.
    E.g. `{:string, transform: &String.downcase/1}`.
  * `:validate` – a function that must return `true` for the value to be accepted.
    Runs after `:transform` and `:in`. Fails with reason `:validation_failed` on `false`.
    E.g. `{:integer, validate: &(rem(&1, 2) == 0)}`.

Execution order: parse → transform → in → validate.

Errors include a `:trace` that points to the exact failing path (list indexes and/or map keys).

# `atom_type`

```elixir
@type atom_type() ::
  {:atom,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :atom
```

Atom type. Accepts atoms and strings convertible to existing atoms
(via `String.to_existing_atom/1`).

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse({:atom, in: [:draft, :published, :archived]}, "draft")
    {:ok, :draft}

    iex> Mold.parse(:atom, "nonexistent_atom_xxx")
    {:error, [%Mold.Error{reason: :unknown_atom, value: "nonexistent_atom_xxx"}]}

    iex> Mold.parse({:atom, in: [:draft, :published]}, "archived")
    {:error, [%Mold.Error{reason: {:not_in, [:draft, :published]}, value: :archived}]}

    iex> Mold.parse({:atom, default: :draft}, nil)
    {:ok, :draft}

# `boolean_type`

```elixir
@type boolean_type() ::
  {:boolean,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :boolean
```

Boolean type. Accepts booleans, strings (`"true"`, `"false"`, `"1"`, `"0"`), and integers (`1`, `0`).

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:boolean, "true")
    {:ok, true}

    iex> Mold.parse(:boolean, "0")
    {:ok, false}

    iex> Mold.parse(:boolean, "yes")
    {:error, [%Mold.Error{reason: :invalid_format, value: "yes"}]}

    iex> Mold.parse({:boolean, default: false}, nil)
    {:ok, false}

# `float_type`

```elixir
@type float_type() ::
  {:float,
   nilable: boolean(),
   default: default(),
   min: number(),
   max: number(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :float
```

Float type. Accepts floats, integers (promoted to float), and strings parseable as floats.

Options:
- `:min` – minimum value (inclusive)
- `:max` – maximum value (inclusive)
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:float, "3.14")
    {:ok, 3.14}

    iex> Mold.parse(:float, 42)
    {:ok, 42.0}

    iex> Mold.parse({:float, min: 0.0, max: 1.0}, "1.5")
    {:error, [%Mold.Error{reason: {:too_large, max: 1.0}, value: 1.5}]}

# `integer_type`

```elixir
@type integer_type() ::
  {:integer,
   nilable: boolean(),
   default: default(),
   min: integer(),
   max: integer(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :integer
```

Integer type. Accepts integers and strings parseable as integers.

Options:
- `:min` – minimum value (inclusive)
- `:max` – maximum value (inclusive)
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:integer, "42")
    {:ok, 42}

    iex> Mold.parse({:integer, min: 0, max: 100}, "150")
    {:error, [%Mold.Error{reason: {:too_large, max: 100}, value: 150}]}

    iex> Mold.parse(:integer, "abc")
    {:error, [%Mold.Error{reason: :invalid_format, value: "abc"}]}

    iex> Mold.parse({:integer, in: 1..10}, "5")
    {:ok, 5}

# `string_type`

```elixir
@type string_type() ::
  {:string,
   trim: boolean(),
   nilable: boolean(),
   default: default(),
   format: Regex.t(),
   min_length: non_neg_integer(),
   max_length: non_neg_integer(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :string
```

String type. Accepts binaries; trims whitespace by default.

Empty strings (and whitespace-only with trim) are treated as `nil`.

Options:
- `:trim` – trim whitespace before validation (default: `true`)
- `:format` – validate against a `t:Regex.t/0` pattern
- `:min_length` – minimum string length (inclusive, in grapheme clusters)
- `:max_length` – maximum string length (inclusive, in grapheme clusters)
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:string, "  hello  ")
    {:ok, "hello"}

    iex> Mold.parse(:string, "   ")
    {:error, [%Mold.Error{reason: :unexpected_nil, value: "   "}]}

    iex> Mold.parse({:string, nilable: true}, "")
    {:ok, nil}

    iex> Mold.parse({:string, min_length: 3, max_length: 50}, "hi")
    {:error, [%Mold.Error{reason: {:too_short, min_length: 3}, value: "hi"}]}

    iex> format = ~r/^[a-z]+$/
    iex> Mold.parse({:string, format: format}, "Hello")
    {:error, [%Mold.Error{reason: {:invalid_format, format}, value: "Hello"}]}

    iex> Mold.parse({:string, trim: false}, "  hello  ")
    {:ok, "  hello  "}

# `date_type`

```elixir
@type date_type() ::
  {:date,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :date
```

Date type. Accepts `t:Date.t/0` or ISO8601 date string.
Empty strings are treated as `nil`.

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:date, "2024-01-02")
    {:ok, ~D[2024-01-02]}

    iex> Mold.parse(:date, "2024-13-01")
    {:error, [%Mold.Error{reason: :invalid_date, value: "2024-13-01"}]}

    iex> year_2024 = Date.range(~D[2024-01-01], ~D[2024-12-31])
    iex> Mold.parse({:date, in: year_2024}, "2025-01-01")
    {:error, [%Mold.Error{reason: {:not_in, year_2024}, value: ~D[2025-01-01]}]}

# `datetime_type`

```elixir
@type datetime_type() ::
  {:datetime,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :datetime
```

DateTime type. Accepts `t:DateTime.t/0` or ISO8601 datetime string.
Empty strings are treated as `nil`.

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:datetime, "2024-01-02T03:04:05Z")
    {:ok, ~U[2024-01-02 03:04:05Z]}

    iex> Mold.parse(:datetime, ~U[2024-01-02 03:04:05Z])
    {:ok, ~U[2024-01-02 03:04:05Z]}

    iex> Mold.parse(:datetime, "invalid")
    {:error, [%Mold.Error{reason: :invalid_format, value: "invalid"}]}

    iex> Mold.parse({:datetime, nilable: true}, "")
    {:ok, nil}

# `naive_datetime_type`

```elixir
@type naive_datetime_type() ::
  {:naive_datetime,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :naive_datetime
```

NaiveDateTime type. Accepts `t:NaiveDateTime.t/0` or ISO8601 datetime string (without timezone).
Empty strings are treated as `nil`.

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:naive_datetime, "2024-01-02T03:04:05")
    {:ok, ~N[2024-01-02 03:04:05]}

    iex> Mold.parse(:naive_datetime, "invalid")
    {:error, [%Mold.Error{reason: :invalid_format, value: "invalid"}]}

# `time_type`

```elixir
@type time_type() ::
  {:time,
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :time
```

Time type. Accepts `t:Time.t/0` or ISO8601 time string.
Empty strings are treated as `nil`.

Options:
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse(:time, "14:30:00")
    {:ok, ~T[14:30:00]}

    iex> Mold.parse(:time, "25:00:00")
    {:error, [%Mold.Error{reason: :invalid_time, value: "25:00:00"}]}

# `list_type`

```elixir
@type list_type() ::
  {:list,
   type: t(),
   reject_invalid: boolean(),
   min_length: non_neg_integer(),
   max_length: non_neg_integer(),
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :list
```

List type. Validates each element against the given type.

Bare `:list` validates the value is a list and returns as-is (passthrough).

The shortcut `[type]` can always be used instead of `{:list, type: type}`,
including with options: `{[type], opts}`.

Options:
- `:type` – the element type `t:t/0` (required)
- `:reject_invalid` – drop invalid items instead of failing
- `:min_length` – minimum list length (inclusive)
- `:max_length` – maximum list length (inclusive)
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse([:string], ["a", "b"])
    {:ok, ["a", "b"]}

    iex> Mold.parse([%{name: :string}], [%{"name" => "A"}, %{"name" => "B"}])
    {:ok, [%{name: "A"}, %{name: "B"}]}

    iex> Mold.parse({[:integer], min_length: 1, max_length: 3}, [])
    {:error, [%Mold.Error{reason: {:too_short, min_length: 1}, value: []}]}

    iex> Mold.parse({[:string], reject_invalid: true}, ["a", nil, "b"])
    {:ok, ["a", "b"]}

    iex> Mold.parse([:integer], ["1", "abc", "3"])
    {:error, [%Mold.Error{reason: :invalid_format, value: "abc", trace: [1]}]}

# `map_type`

```elixir
@type map_type() ::
  {:map,
   fields: [
     {term(), t() | [type: t(), source: source_path(), optional: boolean()]}
   ],
   keys: t(),
   values: t(),
   source: (term() -&gt; any()),
   reject_invalid: boolean(),
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :map
```

Map type. Three forms:

- Bare `:map` — validates the value is a map, returns as-is (passthrough).
- `{:map, fields: [...]}` — validates maps field-by-field. Field names are typically atoms
  (the default `source` converts them to strings for lookup); non-atom keys require a custom `source`.
- `{:map, keys: t, values: t}` — homogeneous map, parses all keys and values.

Options for `fields`:
- `:fields` – list of `{name, type}` tuples where each value is either a `t:t/0` (`name: :string`)
  or a keyword list with field options:
  - `:type` – the field type `t:t/0` (required)
  - `:source` – where to read the value from. A single step or a list of steps (path),
    like `get_in/2`. Each step can be:
    - function – `Access` accessor (e.g. `Access.at/1`, `Access.elem/1`, `Access.key/2`)
    - any other term – key lookup via `Access.fetch/2` (string, atom, integer, etc.)
  - `:optional` – when `true`, omit field from result when missing from input
- `:source` – a function `(field_name -> any())` that derives the source key from each field name
  (e.g. `source: &(Atom.to_string(&1) |> Macro.camelize())`).
  Defaults to `&Atom.to_string/1`. Propagates recursively to nested maps, including through lists and tuples.

Options for `keys`/`values`:
- `:keys` – type `t:t/0` for all keys
- `:values` – type `t:t/0` for all values

Both `fields` and `keys`/`values` forms support:
- `:reject_invalid` – drop items that fail parsing instead of returning an error.
  For `fields`, only affects fields marked `optional: true` — required fields still fail.
  For `keys`/`values`, drops the key-value pair that failed.

[Shared options](#module-shared-options) apply to all forms.

### Examples

    iex> Mold.parse(%{name: :string, age: :integer}, %{"name" => "Alice", "age" => "25"})
    {:ok, %{name: "Alice", age: 25}}

Source defaults to `&Atom.to_string/1` — field names are looked up as string keys.
Custom source per field:

    iex> schema = {:map, fields: [
    ...>   user_name: [type: :string, source: "userName"],
    ...>   is_active: [type: :boolean, source: "isActive"]
    ...> ]}
    iex> Mold.parse(schema, %{"userName" => "Alice", "isActive" => "true"})
    {:ok, %{user_name: "Alice", is_active: true}}

Nested source paths:

    iex> schema = {:map, fields: [name: [type: :string, source: ["details", "name"]]]}
    iex> Mold.parse(schema, %{"details" => %{"name" => "Alice"}})
    {:ok, %{name: "Alice"}}

`Access` functions for advanced navigation — `Access.at/1` for lists,
`Access.elem/1` for tuples, `Access.key/2` with a default, and more:

    iex> schema = {:map, fields: [
    ...>   lat: [type: :float, source: ["coords", Access.at(0)]],
    ...>   lng: [type: :float, source: ["coords", Access.at(1)]]
    ...> ]}
    iex> Mold.parse(schema, %{"coords" => [49.8, 24.0]})
    {:ok, %{lat: 49.8, lng: 24.0}}

Global source function (propagates to nested maps):

    iex> schema = {
    ...>   %{user_name: :string, address: %{zip_code: :string}},
    ...>   source: &(Atom.to_string(&1) |> Macro.camelize())
    ...> }
    iex> Mold.parse(schema, %{"UserName" => "Alice", "Address" => %{"ZipCode" => "10001"}})
    {:ok, %{user_name: "Alice", address: %{zip_code: "10001"}}}

Non-atom field names work with a custom `source`:

    iex> schema = {:map,
    ...>   source: fn {ns, name} -> Enum.join([ns, name], ":") end,
    ...>   fields: [
    ...>     {{:feature, :dark_mode}, :boolean},
    ...>     {{:feature, :beta}, :boolean}
    ...>   ]}
    iex> Mold.parse(schema, %{"feature:dark_mode" => "true", "feature:beta" => "0"})
    {:ok, %{{:feature, :dark_mode} => true, {:feature, :beta} => false}}

Optional fields:

    iex> schema = {:map, fields: [
    ...>   name: :string,
    ...>   bio: [type: :string, optional: true]
    ...> ]}
    iex> Mold.parse(schema, %{"name" => "Alice"})
    {:ok, %{name: "Alice"}}
    iex> Mold.parse(schema, %{"name" => "Alice", "bio" => "hello"})
    {:ok, %{name: "Alice", bio: "hello"}}
    iex> # optional omits the field when missing, but nil still fails — use nilable on the type
    iex> Mold.parse(schema, %{"name" => "Alice", "bio" => nil})
    {:error, [%Mold.Error{reason: :unexpected_nil, value: nil, trace: ["bio"]}]}

When a field is both `optional: true` and has a `default`, `optional` takes priority
for missing fields — the field is omitted from the result. The `default` only applies
when the field is present but `nil`:

    iex> schema = {:map, fields: [
    ...>   bio: [type: {:string, default: "none"}, optional: true]
    ...> ]}
    iex> Mold.parse(schema, %{})
    {:ok, %{}}
    iex> Mold.parse(schema, %{"bio" => nil})
    {:ok, %{bio: "none"}}

Missing fields are errors:

    iex> Mold.parse(%{name: :string}, %{})
    {:error, [%Mold.Error{reason: {:missing_field, "name"}, value: %{}, trace: []}]}

Homogeneous maps with `keys`/`values`:

    iex> Mold.parse({:map, keys: :string, values: :integer}, %{"a" => "1", "b" => "2"})
    {:ok, %{"a" => 1, "b" => 2}}

    iex> Mold.parse({:map, keys: :atom, values: :string}, %{"name" => "Alice"})
    {:ok, %{name: "Alice"}}

Reject invalid — for `fields`, only optional fields are affected:

    iex> schema = {:map, reject_invalid: true, fields: [
    ...>   name: :string,
    ...>   age: [type: :integer, optional: true],
    ...>   bio: [type: :string, optional: true]
    ...> ]}
    iex> Mold.parse(schema, %{"name" => "Alice", "age" => "nope", "bio" => nil})
    {:ok, %{name: "Alice"}}
    iex> Mold.parse(schema, %{"age" => "25"})
    {:error, [%Mold.Error{reason: {:missing_field, "name"}, value: %{"age" => "25"}, trace: []}]}

Reject invalid — for `keys`/`values`, drops the key-value pair that failed:

    iex> Mold.parse({:map, keys: :string, values: :integer, reject_invalid: true}, %{"a" => "1", "b" => "nope"})
    {:ok, %{"a" => 1}}

# `tuple_type`

```elixir
@type tuple_type() ::
  {:tuple,
   elements: [t()],
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | :tuple
```

Tuple type. Validates each element positionally. Accepts both tuples and lists as input.

Bare `:tuple` validates the value is a tuple (or list), converts lists to tuples, returns as-is.

Options:
- `:elements` – list of `t:t/0`, one per element (required)
- [Shared options](#module-shared-options)

### Examples

    iex> Mold.parse({:tuple, elements: [:string, :integer]}, ["Alice", "25"])
    {:ok, {"Alice", 25}}

    iex> Mold.parse({:tuple, elements: [:string, :integer]}, {"Alice", "25"})
    {:ok, {"Alice", 25}}

    iex> Mold.parse({:tuple, elements: [:string, :integer]}, ["only_one"])
    {:error, [%Mold.Error{reason: {:unexpected_length, expected: 2, got: 1}, value: ["only_one"]}]}

    iex> Mold.parse(:tuple, [1, "two", :three])
    {:ok, {1, "two", :three}}

# `union_type`

```elixir
@type union_type() ::
  {:union,
   by: (any() -&gt; any()),
   of: %{required(any()) =&gt; t()},
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
```

Union type. Selects which type to apply based on the value.

The `by` function receives the raw value and must return a variant
that is looked up in `of`.

Options:
- `:by` – function that takes the raw value and returns a variant (required)
- `:of` – map of variant => `t:t/0` (required)
- [Shared options](#module-shared-options)

### Examples

    iex> schema = {:union,
    ...>   by: fn value -> value["type"] end,
    ...>   of: %{
    ...>     "user" => %{name: :string},
    ...>     "bot"  => %{version: :integer}
    ...>   }}
    iex> Mold.parse(schema, %{"type" => "user", "name" => "Alice"})
    {:ok, %{name: "Alice"}}
    iex> Mold.parse(schema, %{"type" => "bot", "version" => "3"})
    {:ok, %{version: 3}}
    iex> Mold.parse(schema, %{"type" => "admin"})
    {:error, [%Mold.Error{reason: {:unknown_variant, "admin"}, value: %{"type" => "admin"}}]}

Use a catch-all in `by` to handle unexpected input types:

    iex> schema = {:union,
    ...>   by: fn
    ...>     %{"type" => type} -> type
    ...>     _ -> :unknown
    ...>   end,
    ...>   of: %{
    ...>     "user" => %{name: :string},
    ...>     "bot"  => %{version: :integer}
    ...>   }}
    iex> Mold.parse(schema, 123)
    {:error, [%Mold.Error{reason: {:unknown_variant, :unknown}, value: 123}]}

# `function_type`

```elixir
@type function_type() ::
  {parse_function(),
   nilable: boolean(),
   default: default(),
   in: Enumerable.t(),
   transform: transform(),
   validate: validate()}
  | parse_function()
```

Function type. A bare `t:parse_function/0` or a tuple with [shared options](#module-shared-options).

### Examples

    iex> email_type = fn v ->
    ...>   if is_binary(v) and String.contains?(v, "@") do
    ...>     {:ok, String.downcase(v)}
    ...>   else
    ...>     {:error, :invalid_email}
    ...>   end
    ...> end
    iex> Mold.parse(email_type, "USER@EXAMPLE.COM")
    {:ok, "user@example.com"}
    iex> Mold.parse({email_type, nilable: true}, nil)
    {:ok, nil}
    iex> Mold.parse({email_type, default: "fallback@example.com"}, nil)
    {:ok, "fallback@example.com"}
    iex> # Use inside maps
    iex> Mold.parse(%{email: email_type}, %{"email" => "USER@EXAMPLE.COM"})
    {:ok, %{email: "user@example.com"}}
    iex> # Standard library functions work as types
    iex> Mold.parse(&JSON.decode/1, ~s|{"a": 1}|)
    {:ok, %{"a" => 1}}

Bare `:error` is supported (see `t:parse_function/0`):

    iex> Mold.parse(&Version.parse/1, "1.0.0")
    {:ok, %Version{major: 1, minor: 0, patch: 0}}
    iex> Mold.parse(&Version.parse/1, "invalid")
    {:error, [%Mold.Error{reason: :invalid, value: "invalid"}]}

# `parse_function`

```elixir
@type parse_function() :: (any() -&gt;
                       {:ok, any()}
                       | {:error, [Mold.Error.t()] | any()}
                       | :error)
```

A function that attempts to parse a value.

Must return `{:ok, parsed}`, `{:error, reason}`, or bare `:error`.

Bare `:error` is converted to `{:error, :invalid}` — this lets you use
standard library functions like `&Version.parse/1` directly as types.

Can also return `{:error, [%Mold.Error{}]}` — errors are passed through
with trace propagation. This is useful when a parse function calls
`Mold.parse/2` internally, including recursive types:

    defmodule Comment do
      def parse_comment(value) do
        Mold.parse(%{text: :string, replies: {[&parse_comment/1], nilable: true}}, value)
      end
    end

# `default`

```elixir
@type default() :: any() | (-&gt; any()) | {module(), atom(), [any()]}
```

Default value: a static value, a zero-arity function, or an MFA tuple.

# `source_path`

```elixir
@type source_path() :: source_step() | [source_step()]
```

# `source_step`

```elixir
@type source_step() :: term() | Access.access_fun(any(), any())
```

# `t`

```elixir
@type t() ::
  string_type()
  | atom_type()
  | boolean_type()
  | integer_type()
  | float_type()
  | datetime_type()
  | naive_datetime_type()
  | date_type()
  | time_type()
  | union_type()
  | map_type()
  | list_type()
  | tuple_type()
  | function_type()
  | %{optional(any()) =&gt; t()}
  | {%{optional(any()) =&gt; t()}, keyword()}
  | [t()]
  | {[t()], keyword()}
```

Union of all built-in and custom types accepted by `parse/2` and `parse!/2`.

# `transform`

```elixir
@type transform() :: (any() -&gt; any())
```

Transform function applied to the parsed value.

# `validate`

```elixir
@type validate() :: (any() -&gt; boolean())
```

Validate function. Must return `true` for the value to be accepted.

# `parse`

```elixir
@spec parse(t(), data :: any()) ::
  {:ok, result :: any()} | {:error, [Mold.Error.t(), ...]}
```

Parses `data` according to `type` and returns `{:ok, value}` or `{:error, [%Mold.Error{}]}`.

See `t:t/0` for all accepted type forms.

    iex> Mold.parse({:string, nilable: true, trim: true}, "  ")
    {:ok, nil}

    iex> Mold.parse(:boolean, "false")
    {:ok, false}

# `parse!`

```elixir
@spec parse!(t(), any()) :: any() | no_return()
```

Parses `data` according to `type` or raises `t:Mold.Error.t/0` on failure.

    iex> Mold.parse!(%{name: :string}, %{"name" => "Bob"})
    %{name: "Bob"}

Multiple errors are combined into a single exception:

    Mold.parse!(%{name: :string, age: :integer}, %{"name" => nil, "age" => "abc"})
    #=> ** (Mold.Error) Unable to parse data
    #=>
    #=> 2 errors:
    #=>   1. :unexpected_nil at ["name"] (value: nil)
    #=>   2. :invalid_format at ["age"] (value: "abc")

---

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