# `Zizq.Error`
[🔗](https://github.com/zizq-labs/zizq-elixir/blob/v0.8.0/lib/zizq/error.ex#L4)

The error returned by every `Zizq` function.

Every failure arrives as `{:error, %Zizq.Error{}}` — a request the
server rejected, a server that could not be reached, a body that
would not encode — so there is a single shape to match on wherever
the problem occurred. The `:reason` field says which kind it was.

    case Zizq.enqueue(job, MyApp.Zizq) do
      {:ok, job} ->
        job

      {:error, %Zizq.Error{reason: :forbidden}} ->
        needs_a_pro_license()

      {:error, %Zizq.Error{} = error} ->
        Logger.error(Exception.message(error))
    end

Reasons are atoms, so a guard covers a whole class of failure when
the specific one does not matter:

    {:error, %Zizq.Error{reason: reason}} when reason in [:not_found, :conflict] ->
      :already_handled

## Fields

  * `:reason` — the kind of failure; see `t:reason/0`
  * `:message` — human-readable, preferring the server's own wording
  * `:status` — the HTTP status, or `nil` when no response arrived
  * `:body` — the decoded response body, when there was one
  * `:cause` — the underlying exception behind a transport or codec
    failure, so the original detail is still available

## Deciding whether to retry

Use `retryable?/1` rather than inspecting the status yourself. It
keeps the policy in one place, and it is the same policy the worker
applies to acknowledgements: transport failures and 5xx are
transient, everything else is permanent.

# `reason`

```elixir
@type reason() ::
  :not_found
  | :conflict
  | :forbidden
  | :unsupported_format
  | :invalid_request
  | :client_error
  | :server_error
  | :transport
  | :encode
  | :decode
  | :unexpected_status
```

Why the call failed.

  * `:not_found` — 404. A job or cron entry that does not exist.
  * `:conflict` — 409.
  * `:forbidden` — 403. The server refused a licensed feature; see
    the message for which one.
  * `:unsupported_format` — 406 or 415. Content negotiation failed,
    which indicates a client bug rather than bad input.
  * `:invalid_request` — 400 or 422. The server rejected the request.
  * `:client_error` — any other 4xx.
  * `:server_error` — any 5xx.
  * `:transport` — the request never completed: connection refused,
    timeout, DNS failure, pool unavailable.
  * `:encode` — the request body could not be serialised.
  * `:decode` — the response body could not be deserialised.
  * `:unexpected_status` — the server answered with a status this
    endpoint does not know how to interpret. A client bug, or a
    server newer than this client.

# `t`

```elixir
@type t() :: %Zizq.Error{
  __exception__: term(),
  body: term(),
  cause: Exception.t() | nil,
  message: String.t(),
  reason: reason(),
  status: non_neg_integer() | nil
}
```

# `decode`

```elixir
@spec decode(Exception.t()) :: t()
```

Wrap a response body that could not be deserialised.

# `encode`

```elixir
@spec encode(Exception.t()) :: t()
```

Wrap a request body that could not be serialised.

# `from_response`

```elixir
@spec from_response(non_neg_integer(), term()) :: t()
```

Build an error from a non-success HTTP response.

Callers decide which statuses count as failures; the transport layer
passes every status through untouched. That matters because status
alone is not always enough: a 422 from the bulk acknowledge endpoint
reports *partial success* (`{"not_found" => [...]}`, the rest having
completed), where a 422 elsewhere is a genuine rejection.

# `retryable?`

```elixir
@spec retryable?(t()) :: boolean()
```

Whether retrying the same request could plausibly succeed.

True for `:transport` and `:server_error`; false for everything else.
A 4xx will fail identically however many times it is sent, and a
body that would not encode will not encode on a second attempt.

    iex> Zizq.Error.retryable?(%Zizq.Error{reason: :server_error})
    true

    iex> Zizq.Error.retryable?(%Zizq.Error{reason: :not_found})
    false

# `transport`

```elixir
@spec transport(Exception.t()) :: t()
```

Wrap a transport-level failure.

---

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