# `Ramal.Error`
[🔗](https://github.com/agustinrojas1/ramal/blob/v0.2.0/lib/ramal/error.ex#L1)

The error every Ramal function returns as `{:error, %Ramal.Error{}}`.

When a Ramal error is raised inside an Ash action (for example a bulk action inside a branch), Ash returns it in
the `errors` of an `Ash.Error.Invalid`; `find/2` pulls it out.

`reason` is an atom from the closed list below, so a host can match on it. `message` says what happened in words
a developer can act on. `details` carries the data behind the message, for example the stale rows.

| reason | meaning |
|---|---|
| `:not_found` | no branch with that id, in memory or in storage |
| `:not_open` | the branch is not in the status the call needs: open for `run/2`, `fork/2`, `merge/2` and `propose/2`, proposed for `approve/2` and `reject/2` |
| `:stale` | the base moved under rows the branch read (`details.rows`) |
| `:merge_failed` | a replayed action failed; `details` has `index`, `type`, `resource`, `action` and the Ash `error` |
| `:database_generated_key` | a branch cannot create a row whose primary key the database generates |
| `:bulk_unsupported` | a bulk create, update or destroy inside a branch |
| `:upsert_unsupported` | an upsert inside a branch |
| `:multitenancy_unsupported` | a multitenant resource used with a tenant inside a branch |
| `:branch_too_large` | the branch process exceeded `max_heap_size` and was stopped |
| `:no_branchable_resources` | the domain has no resource that opted in with `Ramal.Resource` |
| `:nested_branch` | `Ramal.run/2` was called inside another `Ramal.run/2`, or `Ramal.merge/2` inside one |
| `:missing_snapshot` | an overlay entry that needs the base row as first seen has none recorded |
| `:limit_reached` | `Ramal.branch/2` or `Ramal.fork/2` was refused by `:max_active_branches` or `:branch_rate` of `Ramal.Supervisor` (`details.limit`, `max`, and `active` or `window`) |
| `:storage` | the storage backend failed |

## Example

    iex> error = Ramal.Error.new(:not_open, "branch b1 is already merged", %{status: :merged})
    iex> {error.reason, error.message, error.details}
    {:not_open, "branch b1 is already merged", %{status: :merged}}
    iex> Exception.message(error)
    "branch b1 is already merged"

# `reason`

```elixir
@type reason() ::
  :not_found
  | :not_open
  | :stale
  | :merge_failed
  | :database_generated_key
  | :bulk_unsupported
  | :upsert_unsupported
  | :multitenancy_unsupported
  | :branch_too_large
  | :no_branchable_resources
  | :nested_branch
  | :missing_snapshot
  | :limit_reached
  | :storage
```

# `t`

```elixir
@type t() :: %Ramal.Error{
  __exception__: true,
  bread_crumbs: term(),
  class: term(),
  details: map(),
  message: String.t(),
  path: term(),
  reason: reason(),
  splode: term(),
  stacktrace: term(),
  vars: term()
}
```

# `exception`

```elixir
@spec exception(opts :: Keyword.t()) :: %Ramal.Error{
  __exception__: true,
  bread_crumbs: term(),
  class: term(),
  details: term(),
  message: term(),
  path: term(),
  reason: term(),
  splode: term(),
  stacktrace: term(),
  vars: term()
}
```

Create an `Elixir.Ramal.Error` without raising it.

## Keys

- :reason
- :message
- :details

# `find`

```elixir
@spec find(term()) :: t() | nil
```

The first `Ramal.Error` in an error Ash returned, or `nil`.

Ash wraps the errors of an action in an error class (`Ash.Error.Invalid` and friends) whose `errors` list holds
them. This looks through that list, so a host can match on the reason without knowing how Ash nested it.

## Example

    iex> error = Ramal.Error.new(:bulk_unsupported, "no bulk inside a branch")
    iex> Ramal.Error.find(Ash.Error.to_error_class(error)).reason
    :bulk_unsupported
    iex> Ramal.Error.find(Ash.Error.to_error_class("something else"))
    nil

# `new`

```elixir
@spec new(reason(), String.t(), map()) :: t()
```

Builds an error.

## Example

    iex> Ramal.Error.new(:not_found, "no branch with id \"b1\"").reason
    :not_found

---

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