TypeSafeAPI.Error exception (TypeSafe AI v0.1.0-alpha.3)

Copy Markdown View Source

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

Summary

Functions

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

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

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

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

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

Types

t()

@type t() :: %TypeSafeAPI.Error{
  __exception__: true,
  body: term(),
  headers: %{required(String.t()) => [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()

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

Functions

from_exception(exception)

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

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

from_response(response)

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

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

retryable?(error)

@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(message, body \\ nil, opts \\ [])

@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(message)

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

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