# `Latu.Error`
[🔗](https://github.com/zero-one-group/latu/blob/v0.4.0/lib/latu/error.ex#L1)

Anything Latu returns as `{:error, _}`.

`kind` says which side failed, so callers can match without parsing messages:

  * `:rpc` — the server refused or failed the call; Spark's own detail is below.
  * `:protocol` — the server answered, but not in a shape the protocol allows (a stream that
    ended without completing, a batch out of order, a `Config` reply with the wrong count).
    Latu refused to guess. Not a plan problem, and not something a retry of the plan fixes.
  * `:connect`, `:invalid_url`, `:session` — before or around the call: the channel, the URL,
    the session id.
  * `:decode` — the result arrived and Latu could not turn it into what you asked for; the
    message names the column or the bound.

## What a Spark error carries

For a `:rpc` error, Spark puts structured detail in the gRPC trailers and Latu unpacks it —
no extra round trip, because it arrives with the failure:

  * `error_class` — Spark's own error class, like `"UNRESOLVED_COLUMN.WITH_SUGGESTION"`. The
    thing Spark's error documentation is indexed by, and the right thing to match on.
  * `sql_state` — the SQLSTATE, like `"42703"`.
  * `classes` — the JVM exception hierarchy, most specific first. `"AnalysisException"` in
    here means the plan was refused rather than the query failed.
  * `parameters` — the message's own parameters, so you can read a value out rather than
    parsing it back out of the sentence.
  * `stacktrace` — the server's stack trace as one string, when the server was configured to
    send one. **Not** in `message/1`: a JVM trace is not what you want in a REPL, and it is
    one field away when you do.
  * `error_id` — the handle `Latu.error_details/2` fetches the full cause chain with.
  * `retry_delay` — milliseconds the server asked the client to wait before trying again,
    when it attached a `RetryInfo`; `nil` otherwise. Anything carrying one is retried.
  * `causes` — only populated by `Latu.error_details/2`. One entry per exception in the
    chain, root cause last. The same call restores a `message` the server abbreviated to
    2048 characters on the wire.

`status` and `details` are `GRPC.RPCError`'s, `:rpc` only; `details` keeps the raw trailer
in case something Latu does not read is in it.

# `cause`

```elixir
@type cause() :: %{
  message: String.t(),
  classes: [String.t()],
  error_class: String.t() | nil,
  stacktrace: [String.t()]
}
```

One exception in a server-side cause chain. See `Latu.error_details/2`.

# `t`

```elixir
@type t() :: %Latu.Error{
  __exception__: term(),
  causes: [cause()],
  classes: [String.t()],
  details: term(),
  error_class: String.t() | nil,
  error_id: String.t() | nil,
  kind: atom(),
  message: String.t(),
  parameters: %{optional(String.t()) =&gt; String.t()},
  retry_delay: non_neg_integer() | nil,
  sql_state: String.t() | nil,
  stacktrace: String.t() | nil,
  status: non_neg_integer() | nil
}
```

# `message`

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

What a raised `Latu.Error` prints.

The server's own message, which for a Spark error already reads
`[ERROR_CLASS] ... SQLSTATE: xxx` — so the class is prefixed only when Spark did not put it
there itself, and never twice. The stack trace is deliberately absent; read `:stacktrace`.

    iex> Exception.message(%Latu.Error{kind: :decode, message: "expected one column, got 3"})
    "expected one column, got 3"

    iex> error = %Latu.Error{kind: :rpc, error_class: "UNRESOLVED_COLUMN", message: "nope"}
    iex> Exception.message(error)
    "[UNRESOLVED_COLUMN] nope"

---

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