ruststream_rdkafka/error.rs
1//! The crate error type shared by the broker, publishers, and subscribers.
2
3use std::error::Error as StdError;
4#[cfg(feature = "schema-registry")]
5use std::time::Duration;
6
7use thiserror::Error;
8
9/// Errors returned by [`KafkaBroker`](crate::KafkaBroker) and the types it hands out.
10///
11/// Underlying [`rdkafka`](https://docs.rs/rdkafka) errors are boxed as sources so the client
12/// library does not leak into this crate's public API surface.
13#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum KafkaError {
16 /// Creating a client failed or the cluster was unreachable during the connect probe.
17 #[error("kafka connection error: {0}")]
18 Connect(#[source] Box<dyn StdError + Send + Sync>),
19
20 /// Publishing a message failed or the broker did not confirm its delivery.
21 #[error("kafka publish error: {0}")]
22 Publish(#[source] Box<dyn StdError + Send + Sync>),
23
24 /// Creating a consumer or subscribing it to its topic failed.
25 #[error("kafka subscribe error: {0}")]
26 Subscribe(#[source] Box<dyn StdError + Send + Sync>),
27
28 /// Receiving a delivery from an open consumer failed.
29 #[error("kafka consume error: {0}")]
30 Consume(#[source] Box<dyn StdError + Send + Sync>),
31
32 /// A handle aliasing the connection was used after the broker shut down.
33 ///
34 /// The lifecycle ladder makes misuse through the owner's handle a compile error:
35 /// [`ConnectedBroker::shutdown`](ruststream::ConnectedBroker::shutdown) consumes the
36 /// connected broker. Publishers paired off it earlier, and subscriptions still open, keep
37 /// aliasing the closed connection, so their operations report this instead of silently
38 /// succeeding against a dead connection.
39 #[error("kafka connection is closed; cannot reach {topic}")]
40 Closed {
41 /// The topic the operation targeted, or the transactional id of a transaction control
42 /// call.
43 topic: String,
44 },
45
46 /// The requested combination of options cannot be executed.
47 ///
48 /// The message names the offending option and the remediation.
49 #[error("invalid options: {0}")]
50 InvalidOptions(String),
51
52 /// `begin_transaction` found a transaction already open on this publisher.
53 ///
54 /// One producer runs one transaction at a time, so a second begin means two flows share
55 /// one publisher; erroring beats silently merging their messages into one transaction.
56 /// Concurrent transactional flows need distinct publishers - one per partition via
57 /// [`TransactionalPartitions`](crate::TransactionalPartitions), or distinct explicit ids.
58 #[error(
59 "a transaction is already open on publisher {id}; one publisher runs one transaction \
60 at a time - use distinct publishers (for example the per-partition set) for \
61 concurrent transactional flows"
62 )]
63 TransactionBusy {
64 /// The transactional id of the publisher that already has an open transaction.
65 id: String,
66 },
67
68 /// `commit` or `abort` was called with no transaction open on this publisher.
69 #[error("no transaction is open on publisher {id}; `begin_transaction` opens one")]
70 NoTransaction {
71 /// The transactional id of the publisher the call was made on.
72 id: String,
73 },
74
75 /// A Schema Registry request failed: unreachable registry, rejected credentials, an
76 /// unknown schema id or subject, or a schema the registry refused.
77 #[cfg(feature = "schema-registry")]
78 #[error("schema registry error: {0}")]
79 SchemaRegistry(#[source] Box<dyn StdError + Send + Sync>),
80
81 /// A Schema Registry request did not answer within the client's request timeout.
82 ///
83 /// A registry that accepts the connection and then goes silent is the case this separates
84 /// from an outright failure: both edges resolve schemas on the async delivery path, so
85 /// without a deadline one silent registry holds a delivery for as long as it stays silent.
86 /// [`SchemaRegistry::request_timeout`](crate::SchemaRegistry::request_timeout) sets the
87 /// deadline.
88 #[cfg(feature = "schema-registry")]
89 #[error("schema registry request {request} did not answer within {timeout:?}")]
90 SchemaRegistryTimeout {
91 /// The registry path the request targeted, for example `/schemas/ids/7`.
92 request: String,
93 /// The deadline that expired.
94 timeout: Duration,
95 },
96
97 /// A value could not be written to, or read from, its binary wire form.
98 ///
99 /// This is the byte lanes' own failure: the Avro or Protobuf encoder rejected the value,
100 /// the payload is not the datum a schema describes, or a delivery does not carry the
101 /// Confluent envelope the reader expected. It stays distinct from
102 /// [`SchemaRegistry`](Self::SchemaRegistry), which is the registry conversation failing -
103 /// the fixed-schema lane reaches no registry at all, so folding the two would make every
104 /// diagnostic point at a component that was never involved.
105 #[cfg(feature = "schema-registry")]
106 #[error("wire format error: {0}")]
107 WireFormat(#[source] Box<dyn StdError + Send + Sync>),
108}
109
110impl KafkaError {
111 pub(crate) fn connect(err: rdkafka::error::KafkaError) -> Self {
112 Self::Connect(Box::new(err))
113 }
114
115 pub(crate) fn publish(err: rdkafka::error::KafkaError) -> Self {
116 Self::Publish(Box::new(err))
117 }
118
119 #[cfg(feature = "schema-registry")]
120 pub(crate) fn schema_registry(err: impl StdError + Send + Sync + 'static) -> Self {
121 Self::SchemaRegistry(Box::new(err))
122 }
123
124 // Only a format's own encoder produces one of these; the envelope's own failures come
125 // through `malformed`, which the frame types report with no format feature enabled.
126 #[cfg(any(feature = "avro", feature = "protobuf"))]
127 pub(crate) fn wire_format(err: impl StdError + Send + Sync + 'static) -> Self {
128 Self::WireFormat(Box::new(err))
129 }
130
131 /// The wire-format failure the lanes report for a payload that is structurally wrong (not
132 /// framed, indexes truncated) rather than for another error's failure.
133 #[cfg(feature = "schema-registry")]
134 pub(crate) fn malformed(message: impl Into<String>) -> Self {
135 Self::WireFormat(message.into().into())
136 }
137
138 pub(crate) fn subscribe(err: rdkafka::error::KafkaError) -> Self {
139 Self::Subscribe(Box::new(err))
140 }
141
142 pub(crate) fn consume(err: rdkafka::error::KafkaError) -> Self {
143 Self::Consume(Box::new(err))
144 }
145}