# `ALLM.Error.ValidationError`
[🔗](https://github.com/cykod/ALLM/blob/v0.3.0/lib/allm/error/validation_error.ex#L1)

Errors returned by `ALLM.Validate` functions.

Layer A — serializable. Carries a list of `t:field_error/0` tuples so
callers can machine-read which fields failed validation and why. Refines
spec §16's "list of error terms" shape into a first-class struct.

See Phase 1 design §Sub-phase 1.1 for the closed reason enum. Phase 8
(sub-phase 8.2) extended the enum with `:invalid_session_input` for the
`ALLM.Session.start/3` / `stream_start/3` input-coercion failure.

> #### BREAKING — Phase 14.4 {: .warning}
>
> `:vision_not_in_v0_2` was removed from the closed reason enum in v0.3
> Phase 14.4. Vision input is now supported via `ALLM.ImagePart` (§35.6);
> the validator no longer short-circuits on image content parts.
> `ValidationError.new(:vision_not_in_v0_2, ...)` raises `ArgumentError`.

# `field_error`

```elixir
@type field_error() :: {field :: atom() | [term()], reason :: atom()}
```

A single field-level validation failure.

The first element is either a single atom (for top-level fields like
`:messages`) or a path of atoms/indices (e.g. `[:messages, 0, :role]`) when
the failure is nested.

# `reason`

```elixir
@type reason() ::
  :invalid_request
  | :invalid_message
  | :invalid_tool
  | :invalid_thread
  | :invalid_session
  | :invalid_session_input
  | :unsupported_capability
  | :invalid_image_request
```

Closed set of validation error reasons (spec §16, §35.2.2, §35.6).

# `t`

```elixir
@type t() :: %ALLM.Error.ValidationError{
  __exception__: true,
  cause: term() | nil,
  errors: [field_error()],
  message: String.t(),
  metadata: map(),
  reason: reason()
}
```

# `new`

```elixir
@spec new(reason(), [field_error()], keyword()) :: t()
```

Build a `%ValidationError{}` from a `reason` atom, a list of `errors`, and
optional keyword fields.

`opts` may include `:message`, `:cause`, and `:metadata`. When `:message` is
omitted, the default is
`"validation failed: #{reason} (#{length(errors)} error(s))"`.

Raises `ArgumentError` if `reason` is not in `t:reason/0` or if `errors` is
not a list.

## Examples

    iex> err = ALLM.Error.ValidationError.new(:invalid_request, [{:messages, :empty}])
    iex> err.reason
    :invalid_request
    iex> err.errors
    [{:messages, :empty}]
    iex> Exception.message(err)
    "validation failed: invalid_request (1 error(s))"

    iex> err = ALLM.Error.ValidationError.new(:invalid_tool, [], message: "nothing wrong")
    iex> Exception.message(err)
    "nothing wrong"

---

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