defmodule DustEcto.Error do @moduledoc """ Single error struct returned from `DustEcto.Repo` when transport failures or server errors occur. Validation errors return `{:error, %Ecto.Changeset{}}` instead — that path is unchanged. ## Kinds * `:network` — Req call failed before reaching the server (connection refused, DNS, TLS, etc.). `retryable?` is `true`. * `:http` — server replied with a non-2xx, non-recognized status. `detail` carries the status + body. Usually retryable on 5xx, not on 4xx. * `:conflict` — `If-Match` precondition failed. `detail` carries `current_revision` and (for batch_write) `op_index` + `path`. * `:not_supported` — feature unavailable on the active transport (e.g. `subscribe` in HTTP mode). Not retryable. * `:nothing_to_write` — `Repo.insert`/`update` had no fields to send. Not retryable; usually a bug in the caller's changeset. * `:timeout` — sync write didn't get an ack within the configured window. The write may still eventually succeed; do not retry blindly. * `:unauthorized` — token rejected by the server. * `:invalid_params` — server rejected the request shape. * `:rate_limited` — server returned 429. `detail` may include `Retry-After`. * `:not_implemented` — server returned a whole-route 404 (the method/path isn't registered). Usually means the deployed Dust server is older than the SDK expects. Distinct from `:not_found`, which is an *entity* miss inside a working route. """ @type kind :: :network | :http | :conflict | :not_supported | :nothing_to_write | :timeout | :unauthorized | :invalid_params | :rate_limited | :not_implemented @type t :: %__MODULE__{ kind: kind(), detail: term(), retryable?: boolean() } defstruct [:kind, :detail, retryable?: false] @doc "Construct an error of the given kind." @spec new(kind(), term(), keyword()) :: t() def new(kind, detail \\ nil, opts \\ []) do %__MODULE__{ kind: kind, detail: detail, retryable?: Keyword.get(opts, :retryable?, default_retryable?(kind)) } end defp default_retryable?(:network), do: true defp default_retryable?(:rate_limited), do: true defp default_retryable?(:timeout), do: false defp default_retryable?(_), do: false end