TypeDB.Error exception (TypeDB v0.2.0)

Copy Markdown View Source

The single exception type raised (or returned) by every TypeDB operation.

Errors carry a :kind describing where the failure came from:

  • :server — TypeDB answered with a structured error body. :code holds the TypeDB error code (e.g. "TSV11", "AUT3", "HSR4"), :status the HTTP status, and :message the server's message, including its cause trace.
  • :transport — the request never produced an HTTP response (connection refused, DNS failure, TLS failure, socket closed).
  • :timeout — the request exceeded the configured timeout.
  • :unauthenticated — credentials were rejected, or the token expired and could not be renewed.
  • :decode — the response body was not valid JSON, or did not match the shape this driver expects.
  • :encode — the mirror of :decode: an Elixir term could not be turned into a TypeDB wire value. A given_rows entry of a type TypeDB has no equivalent for, or a TypeDB.Duration with a negative component, which TypeQL's grammar cannot express. Raised rather than returned, because it happens while the request is still being built and there is nothing to fail.
  • :config — the driver was configured incorrectly. Raised at start-up.

Matching on TypeDB error codes

case TypeDB.query(conn, "social", "match $x isa nonexistent;") do
  {:error, %TypeDB.Error{kind: :server, code: code}} -> {:bad_query, code}
  {:ok, answer} -> answer
end

Error codes are stable across TypeDB releases and are the recommended thing to branch on; messages are not.

Summary

Functions

Builds an error from a TypeDB error response body.

Whether retrying the call that produced this error could plausibly help.

The response statuses the driver treats as retryable by default.

Types

kind()

@type kind() ::
  :server
  | :transport
  | :timeout
  | :unauthenticated
  | :decode
  | :encode
  | :config

t()

@type t() :: %TypeDB.Error{
  __exception__: term(),
  body: term(),
  code: String.t() | nil,
  kind: kind(),
  message: String.t(),
  reason: term(),
  status: pos_integer() | nil
}

Functions

from_response(status, body)

@spec from_response(pos_integer(), term()) :: t()

Builds an error from a TypeDB error response body.

TypeDB error bodies are %{"code" => code, "message" => message}. Anything else is reported verbatim so that no information is lost.

new(kind, message, opts \\ [])

@spec new(kind(), String.t(), keyword()) :: t()

Builds an error.

Public because TypeDB.HTTP is a public extension point and TypeDB.HTTP.request/6 is required to return one of these. Application code should be matching on errors, not constructing them.

TypeDB.Error.new(:transport, "connection refused", reason: :econnrefused)

Options

  • :code — TypeDB's own error code, for :server errors
  • :status — the HTTP status
  • :reason — the underlying term, whatever it was
  • :body — the response body that could not be understood

retryable?(error)

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

Whether retrying the call that produced this error could plausibly help.

case TypeDB.transaction(conn, "social", :write, &steps/1) do
  {:error, %TypeDB.Error{} = error} ->
    if TypeDB.Error.retryable?(error), do: retry_the_whole_thing(), else: give_up(error)

  result ->
    result
end

Note what this is not for. By the time you are holding an error the driver has already retried whatever its configuration allowed, so a true here does not mean it gave up early. This is for the layer above — retrying a whole transaction, requeueing a job — where the unit of work is bigger than one HTTP call and the driver could not have retried it for you.

:server errors are judged by retryable_statuses/0 rather than by a connection's :retry_on_status, because an error does not carry the connection that produced it.

iex> TypeDB.Error.retryable?(TypeDB.Error.new(:transport, "connection refused"))
true

iex> TypeDB.Error.retryable?(TypeDB.Error.new(:server, "no such database", status: 404))
false

iex> TypeDB.Error.retryable?(TypeDB.Error.new(:unauthenticated, "bad password"))
false

retryable_statuses()

@spec retryable_statuses() :: [pos_integer()]

The response statuses the driver treats as retryable by default.

429 is a server shedding load; 502, 503 and 504 are what a proxy, an ingress or a load balancer answers while TypeDB restarts. All four say "not now" rather than "no". This is the default of :retry_on_status.