# `TypeSafeAPI.Error`
[🔗](https://github.com/typesend/typesafe_ai/blob/v0.1.0-alpha.3/lib/typesafe_api/error.ex#L1)

The single error value returned by every `{:error, _}` tuple in this library.

Errors are values, not exceptions, so callers can pattern match on `type` without
rescuing. The bang variants (`TypeSafeAPI.evaluate!/4` and friends) raise this same
struct, which is why it is also an exception.

## Types

  * `:auth` - HTTP 401. The API key is missing or invalid.
  * `:validation` - HTTP 400 or 422 from the API (a malformed body, an
    unknown model, too many options), or a problem this library caught
    locally before sending anything (for example a Score with one level).
    `status` is `nil` for local validation errors.
  * `:rate_limited` - HTTP 429. Retried automatically; you only see it once
    the retry policy gives up.
  * `:overloaded` - HTTP 529 (and 503). Also retried automatically.
  * `:server_error` - any other 5xx. The API failed, not the request.
    Retried automatically; you only see it once the retry policy gives up.
  * `:timeout` - the request exceeded its timeout without a response, the
    server reported one itself (HTTP 408), or a connection pool checkout
    timed out before anything was sent.
  * `:connection` - the request could not reach the server at all.
  * `:unexpected` - anything this library could not make sense of: an
    unknown status, a body that failed to decode, or a response shape it
    does not understand. It never means "the API is having a bad day";
    that is `:server_error`.

`retryable?/1` answers whether the condition is transient, so a caller does
not have to re-derive the retry policy from `status`.

## Fields

  * `status` - the HTTP status, or `nil` when no response was involved
    (local validation, timeouts, connection failures).
  * `message` - a one-line summary, capped at 500 bytes so an HTML error page cannot
    flood a log line (the full body is on `body`). For a 422 the API returns a list of
    `%{"loc" => [...], "msg" => ...}` entries; they are joined as
    `body.questions.dept.criteria: Field required; ...` so the offending
    field is readable without parsing `body`.
  * `body` - the decoded JSON error body, the raw string when it was not
    JSON, or `nil`. Log it for `:unexpected` errors: it shows what changed.
  * `request_id` - the `x-typesafe-request-id` response header. Quote it
    when contacting TypeSafe support.
  * `retry_after_ms` - the wait the server asked for, from `retry-after-ms`
    or `Retry-After`, kept even after retries are exhausted so you can back
    off before the next call or batch.
  * `headers` - the response headers, as `%{name => [value]}`. Empty when no
    response arrived.
  * `retry_count` - how many retries this call burned before giving up.

    case TypeSafeAPI.evaluate(client, state, questions) do
      {:ok, result} -> result
      {:error, %TypeSafeAPI.Error{type: :rate_limited, retry_after_ms: ms}} -> retry_later(ms)
      {:error, %TypeSafeAPI.Error{request_id: id} = error} -> Logger.error(Exception.message(error), request_id: id)
    end

# `t`

```elixir
@type t() :: %TypeSafeAPI.Error{
  __exception__: true,
  body: term(),
  headers: %{required(String.t()) =&gt; [String.t()]},
  message: String.t(),
  request_id: String.t() | nil,
  retry_after_ms: non_neg_integer() | nil,
  retry_count: non_neg_integer(),
  status: pos_integer() | nil,
  type: type()
}
```

# `type`

```elixir
@type type() ::
  :auth
  | :validation
  | :rate_limited
  | :overloaded
  | :server_error
  | :timeout
  | :connection
  | :unexpected
```

# `from_exception`

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

Maps a transport-level exception (no HTTP response) to an error.

# `from_response`

```elixir
@spec from_response(Req.Response.t()) :: t()
```

Maps an HTTP response to an error. Only call this for non-2xx responses.

# `retryable?`

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

Whether the condition that produced this error is transient, i.e. whether
retrying the same call could plausibly succeed.

Matches the default `TypeSafeAPI.Retry` status set (408, 429 and every 5xx)
and the transport failures the policy knows about. It answers "is this worth
trying again", not "is this safe to replay": a `POST` that timed out may
already have been billed, which is why `TypeSafeAPI.Retry` gates replay on
the HTTP method as well.

# `unexpected`

```elixir
@spec unexpected(String.t(), term(), keyword()) :: t()
```

Builds an `:unexpected` error for a response we could not make sense of.

`opts` carries whatever the response did have, so a 2xx with an undecodable
body still reports the status and the request id support will ask for:

  * `:status` - the HTTP status, when a response arrived
  * `:request_id` - the `x-typesafe-request-id` header
  * `:headers` - the response headers, as `%{name => [value]}`
  * `:retry_count` - retries burned before this response

# `validation`

```elixir
@spec validation(String.t()) :: t()
```

Builds a local validation error. Nothing was sent to the API.

---

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