# `Ltix.Errors`
[🔗](https://github.com/DecoyLex/ltix/blob/main/lib/ltix/errors.ex#L1)

Structured error types for LTI 1.3 validation.

Uses Splode for composable, class-based errors. There are three
error classes, each with a default HTTP status code.

| Class       | Status | Meaning                                                  |
|-------------|--------|----------------------------------------------------------|
| `:invalid`  | 400    | Spec-violating input (bad claims, missing params)        |
| `:security` | 401    | Security framework violations (signature, nonce, expiry) |
| `:unknown`  | 500    | Unexpected / catch-all errors                            |

A single failure is returned directly. Several failures found
at once are returned together in their class, such as
`%Ltix.Errors.Invalid{errors: [...]}`.

Use `status_code/1` to get the HTTP status for any Ltix error.

    error = Ltix.Errors.Invalid.MissingClaim.exception(claim: "sub", spec_ref: "Core §5.3")
    Ltix.Errors.status_code(error)
    #=> 400

When Plug is available, all errors also implement `Plug.Exception`,
so Phoenix error views use the correct status automatically.

# `class`

```elixir
@type class() :: %{
  :__struct__ =&gt; class_module(),
  :__exception__ =&gt; true,
  :errors =&gt; [t()],
  :class =&gt; error_class(),
  :bread_crumbs =&gt; [String.t()],
  :vars =&gt; Keyword.t(),
  :stacktrace =&gt; Splode.Stacktrace.t() | nil,
  :context =&gt; map(),
  optional(atom()) =&gt; any()
}
```

# `class_module`

```elixir
@type class_module() ::
  Ltix.Errors.Unknown | Ltix.Errors.Security | Ltix.Errors.Invalid
```

# `error_class`

```elixir
@type error_class() :: :unknown | :security | :invalid
```

# `t`

```elixir
@type t() :: %{
  :__struct__ =&gt; module(),
  :__exception__ =&gt; true,
  :class =&gt; error_class(),
  :bread_crumbs =&gt; [String.t()],
  :vars =&gt; Keyword.t(),
  :stacktrace =&gt; Splode.Stacktrace.t() | nil,
  :context =&gt; map(),
  optional(atom()) =&gt; any()
}
```

# `splode_error?`

# `status_code`

```elixir
@spec status_code(Exception.t()) :: pos_integer()
```

Returns the HTTP status code for an Ltix error.

Each error has a status code derived from its class.

| Class       | Default status |
|-------------|----------------|
| `:invalid`  | 400            |
| `:security` | 401            |
| `:unknown`  | 500            |

Individual errors may override the default for their class. Exceptions
that are not Ltix errors return 500.

## Examples

    iex> error = Ltix.Errors.Invalid.MissingClaim.exception(claim: "sub", spec_ref: "Core §5.3")
    iex> Ltix.Errors.status_code(error)
    400

# `traverse_errors`

Traverses errors, calling `fun` for each leaf error, and returns a nested
map of results grouped by each error's `path`.

See `Splode.traverse_errors/2` for full documentation.

## Example

    iex> Elixir.Ltix.Errors.traverse_errors(error, fn error ->
    ...>   Exception.message(error)
    ...> end)
    %{name: ["name is required"]}

# `unwrap!`

Raises an error if the result is an error, otherwise returns the result

Alternatively, you can use the `defsplode` macro, which does this automatically.

### Options

- `:error_opts` - Options to pass to `to_error/2` when converting the returned error
- `:unknown_error_opts` - Options to pass to the unknown error if the function returns only `:error`.
  not necessary if your function always returns `{:error, error}`.

### Examples

  def function(arg) do
    case do_something(arg) do
      :success -> :ok
      {:success, result} -> {:ok, result}
      {:error, error} -> {:error, error}
    end
  end

  def function!(arg) do
    YourErrors.unwrap!(function(arg))
  end

---

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