Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The error taxonomy

Two types, in two files, answering two different questions.

TypeQuestionLives in
ErrorCodewhat did the broker say?crates/kafka-conn/src/error_code.rs
Errorwhat happened?crates/kafka-conn/src/error.rs

Both are defined in kafka-conn and re-exported upward. That placement is forced: every crate in the workspace, including the connection layer itself, has to classify a broker's answer, and a workspace with two error types would push a From conversion into every call site in it.

Error — distinguishable at the type level

A UI renders these differently, and collapsing them into one string throws that away. A transport error means "the cluster is unreachable"; an authorization error means "ask your admin"; a decode failure means "this is our bug".

VariantMeans
Transportthe socket failed or never opened
ConnectionClosedthe connection died; every in-flight request resolves to this rather than hanging
Timeoutthe caller's deadline passed
Authenticationcredentials rejected, or the handshake could not agree
Authorizationauthenticated, but not permitted
Brokerthe broker answered with an error code
Decodea response did not parse — this one means we are wrong
ReadOnlya read-only client refused a mutating key before touching the network
UnsupportedApino version of this API is speakable by both ends
Unsupportedthe caller asked for something this build cannot express
InvalidRequestmalformed before it went out

Decode is worth calling out separately. Every other variant describes something about the cluster or the caller; Decode describes a bug in this library or a schema that has drifted, and it should be reported rather than retried.

ErrorCode — derived, not transcribed

The table is derived from kafka_protocol::ResponseError, and that is the load-bearing part rather than an implementation detail.

  • retriable() delegates to the crate's own is_retriable(), which encodes what the protocol says rather than what we remember it saying.
  • from_response_error matches ResponseError exhaustively. ResponseError is a plain enum, so when an upstream bump adds a code, that match stops compiling. A new error code becomes a build failure to triage rather than a silent hole in the classification.

Hand-transcribing a 100+ entry table would be correct exactly once.

Three independent axes

kafka-protocol models one of these. The other two are ours, and they are exhaustive matches over our own enum for the same compile-failure reason.

AxisOwnerQuestion
retriable()upstreamwill trying again plausibly help?
needs_metadata_refresh()oursis the caller's view of leadership stale?
needs_coordinator_refresh()oursis the cached group/txn coordinator wrong?

They are genuinely independent — a code can be retriable without needing any refresh (REQUEST_TIMED_OUT), need a metadata refresh (NOT_LEADER_OR_FOLLOWER), or need a coordinator refresh (NOT_COORDINATOR). Modelling them as one enum would force a false choice on the codes that need two. Metadata, routing and the pool is what acts on them.

Unknown(i16) is not optional

kafka-protocol 0.17 knows error codes through Kafka 4.1. The acceptance suite runs against 4.3.1. A broker can and will return a code with no name here, and that is the expected case rather than a corruption signal.

ErrorCode::Unknown(i16) round-trips and renders. It never panics, and it never collapses into a generic failure that discards the number — the number is the only thing anyone can search for.

The unit test is table-driven over every code plus one that no Kafka release defines (30000), asserting it lands in Unknown(30000) and still renders.

Where classification happens

At the point the response is decoded, not at the call site. A response carrying an error code becomes Error::Broker { code, message } — or Error::Authorization where the code is an authorization failure, since that distinction is what a UI needs and recovering it later means matching on the code again.

Per-item results keep their own errors: describe_topics over 500 names returns 500 entries, each independently Ok or Err, and one UNKNOWN_TOPIC_OR_PARTITION does not become the result of the call. See the third invariant.