# `Splode`
[🔗](https://github.com/ash-project/splode/blob/v0.3.1/lib/splode.ex#L5)

Use this module to create your error aggregator and handler.

For example:

```elixir
defmodule MyApp.Errors do
  use Splode, error_classes: [
    invalid: MyApp.Errors.Invalid,
    unknown: MyApp.Errors.Unknown
  ],
  unknown_error: MyApp.Errors.Unknown.Unknown
end
```

## Options

- `:error_classes` - A keyword list mapping error class atoms to error class modules.
  At least one error class must be provided.

- `:unknown_error` - The module to use when an error cannot be converted to a known type.
  This is required.

- `:merge_with` - A list of other Splode modules whose errors should be recognized and
  flattened when combined. Optional.

- `:filter_stacktraces` - A list of modules or module prefixes to filter from stacktraces.
  For each consecutive sequence of frames matching any filter, only the deepest (last) frame
  is kept. This is useful for hiding internal implementation details from error stacktraces.
  Accepts atoms (exact module match) or strings (prefix match). Optional.

  Elixir standard library frames (Enum, Stream, List, Map, etc.) are treated as part of an
  active matching sequence but are not kept as the "deepest" frame. This prevents stdlib
  frames from appearing in filtered stacktraces when they're sandwiched between internal
  module calls.

  ```elixir
  defmodule MyApp.Errors do
    use Splode,
      error_classes: [invalid: MyApp.Errors.Invalid],
      unknown_error: MyApp.Errors.Unknown,
      filter_stacktraces: [MyApp.Internal, "MyApp.Internal."]
  end
  ```

# `from_json`

```elixir
@callback from_json(module(), map()) :: Splode.Error.t()
```

Converts a combination of a module and json input into an Splode exception.

This allows for errors to be serialized and deserialized

# `set_path`

```elixir
@callback set_path(Splode.Error.t() | [Splode.Error.t()], term() | [term()]) ::
  Splode.Error.t() | [Splode.Error.t()]
```

Sets the path on the error or errors

# `splode_error?`

```elixir
@callback splode_error?(term()) :: boolean()
```

Returns true if the given value is a splode error.

# `to_class`

```elixir
@callback to_class(any()) :: Splode.Error.t()
```

Combine errors into an error class

# `to_error`

```elixir
@callback to_error(any()) :: Splode.Error.t()
```

Turns any value into a splode error

# `traverse_errors`

```elixir
@callback traverse_errors(
  Splode.Error.t() | [Splode.Error.t()],
  (Splode.Error.t() -&gt; term())
) :: map()
```

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

This is useful for turning a Splode error into a simple, queryable structure
for testing or display purposes. The `fun` receives each individual (leaf)
error struct and should return a value (typically a formatted string).

## Example

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

Errors with nested paths produce nested maps:

    iex> traverse_errors(error, &Exception.message/1)
    %{user: %{email: ["email is invalid"]}}

Errors with an empty path (i.e., global/root-level errors) are grouped under
the `[]` key.

# `traverse_errors`

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

Handles error class structs (which contain nested `errors` lists) by
recursively descending into them. Leaf errors are passed to `fun` and their
return values are collected into a list at the appropriate path key.

Errors with an empty path are grouped under the `[]` key.

## Example

    Splode.traverse_errors(error, fn error -> Exception.message(error) end)
    #=> %{name: ["name is required"], email: ["email is invalid"]}

---

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