1use std::collections::BTreeSet;
4
5use krabka_client_core::ClientError;
6use thiserror::Error;
7
8pub(crate) fn is_transport_error(
9 error: &krabka_client_core::ClientError,
10 includes_timeout: bool,
11) -> bool {
12 matches!(
13 error,
14 ClientError::Connect { .. }
15 | ClientError::Tls { .. }
16 | ClientError::Sasl { .. }
17 | ClientError::Disconnected
18 | ClientError::Io(_)
19 ) || (includes_timeout && matches!(error, ClientError::Timeout(_)))
20}
21
22#[derive(Debug, Error)]
24#[non_exhaustive]
25pub enum ConsumerError {
26 #[error("client: {0}")]
27 Client(#[from] krabka_client_core::ClientError),
28
29 #[error("protocol: {0}")]
30 Protocol(#[from] krabka_protocol::ProtocolError),
31
32 #[error("rebalance failed: {0}")]
33 RebalanceFailed(String),
34
35 #[error("rebalance listener failed: {0}")]
38 RebalanceListenerFailed(String),
39
40 #[error("invalid configuration: {0}")]
42 InvalidConfig(String),
43
44 #[error("startup failed after joining group: {0}")]
45 StartupAfterJoin(Box<ConsumerError>),
46
47 #[error("not subscribed to any topic")]
48 NotSubscribed,
49
50 #[error("illegal state: {0}")]
51 IllegalState(String),
52
53 #[error("invalid argument: {0}")]
55 InvalidArgument(String),
56
57 #[error("topic '{0}' is invalid")]
60 InvalidTopic(String),
61
62 #[error("the consumer was woken up")]
65 Wakeup,
66
67 #[error(
70 "To use the group management or offset commit APIs, you must provide a valid group.id in the consumer configuration."
71 )]
72 InvalidGroupId,
73
74 #[error("timeout: {message}")]
81 Timeout {
82 message: String,
83 #[source]
84 cause: Option<Box<ConsumerError>>,
85 },
86
87 #[error("invalid seek offset {0}: must be non-negative")]
88 InvalidOffset(i64),
89
90 #[error("no current assignment for partition {topic}-{partition}")]
93 NoCurrentAssignment { topic: String, partition: i32 },
94
95 #[error("offset commit failed with a retriable exception: {0}")]
99 RetriableCommitFailed(Box<ConsumerError>),
100
101 #[error("commit conflict: rejoined since this poll")]
102 CommitInvalid,
103
104 #[error("coordinator unavailable")]
105 CoordinatorUnavailable,
106
107 #[error(
108 "log truncation detected on {topic}-{partition}: fetch offset {fetch_offset} is past the leader's log; safe offset {safe_offset}"
109 )]
110 LogTruncation {
111 topic: String,
112 partition: i32,
113 fetch_offset: i64,
114 safe_offset: i64,
115 },
116
117 #[error("broker error_code {0}")]
118 Server(i16),
119
120 #[error("not authorized to access group: {0}")]
123 GroupAuthorizationFailed(String),
124
125 #[error(
129 "not authorized to access topics: [{}]",
130 .0.iter().map(String::as_str).collect::<Vec<_>>().join(", ")
131 )]
132 TopicAuthorizationFailed(BTreeSet<String>),
133
134 #[error("unexpected error in offset fetch response: error_code {0}")]
137 OffsetFetchFailed(i16),
138
139 #[error(
144 "fenced group.instance.id {0}: another consumer with the same group.instance.id joined the group"
145 )]
146 FencedInstanceId(String),
147
148 #[error(
153 "offset commit failed: the consumer is not part of an active group; it is likely that the consumer was kicked out of the group"
154 )]
155 CommitFailed,
156
157 #[error(
165 "offset commit cannot be completed since the consumer is undergoing a rebalance for group {0}: call poll() and retry"
166 )]
167 RebalanceInProgress(String),
168
169 #[error(
174 "undefined offset with no reset policy for partitions: [{}]",
175 .0.iter().map(|(topic, partition)| format!("{topic}-{partition}")).collect::<Vec<_>>().join(", ")
176 )]
177 NoOffsetForPartition(BTreeSet<(String, i32)>),
178}
179
180impl ConsumerError {
181 pub(crate) fn timeout(message: impl Into<String>) -> Self {
183 Self::Timeout {
184 message: message.into(),
185 cause: None,
186 }
187 }
188
189 pub(crate) fn is_fatal_offset_fetch_error(&self) -> bool {
192 matches!(
193 self,
194 Self::GroupAuthorizationFailed(_)
195 | Self::TopicAuthorizationFailed(_)
196 | Self::OffsetFetchFailed(_)
197 )
198 }
199}
200
201#[cfg(test)]
202mod tests {
203
204 use super::*;
205
206 #[test]
207 fn display_messages() {
208 for (_name, error, expected) in [
209 (
210 "not subscribed",
211 ConsumerError::NotSubscribed,
212 "not subscribed to any topic",
213 ),
214 ("server", ConsumerError::Server(25), "broker error_code 25"),
215 (
216 "invalid config",
217 ConsumerError::InvalidConfig("group_id required".into()),
218 "invalid configuration: group_id required",
219 ),
220 (
221 "group authorization failed",
222 ConsumerError::GroupAuthorizationFailed("workers".into()),
223 "not authorized to access group: workers",
224 ),
225 (
226 "topic authorization failed",
227 ConsumerError::TopicAuthorizationFailed(
228 maplit::btreeset! {"orders".to_string(), "payments".to_string()},
229 ),
230 "not authorized to access topics: [orders, payments]",
231 ),
232 (
233 "offset fetch failed",
234 ConsumerError::OffsetFetchFailed(6),
235 "unexpected error in offset fetch response: error_code 6",
236 ),
237 (
238 "fenced instance id",
239 ConsumerError::FencedInstanceId("instance-a".into()),
240 "fenced group.instance.id instance-a: another consumer with the same group.instance.id joined the group",
241 ),
242 (
243 "commit failed",
244 ConsumerError::CommitFailed,
245 "offset commit failed: the consumer is not part of an active group; it is likely that the consumer was kicked out of the group",
246 ),
247 (
248 "rebalance in progress",
249 ConsumerError::RebalanceInProgress("group-a".into()),
250 "offset commit cannot be completed since the consumer is undergoing a rebalance for group group-a: call poll() and retry",
251 ),
252 (
253 "no current assignment",
254 ConsumerError::NoCurrentAssignment {
255 topic: "t".into(),
256 partition: 9,
257 },
258 "no current assignment for partition t-9",
259 ),
260 (
261 "retriable commit failed",
262 ConsumerError::RetriableCommitFailed(Box::new(ConsumerError::Server(15))),
263 "offset commit failed with a retriable exception: broker error_code 15",
264 ),
265 (
266 "log truncation",
267 ConsumerError::LogTruncation {
268 topic: "t".into(),
269 partition: 3,
270 fetch_offset: 100,
271 safe_offset: 42,
272 },
273 "log truncation detected on t-3: fetch offset 100 is past the leader's log; safe offset 42",
274 ),
275 (
276 "no offset for partition",
277 ConsumerError::NoOffsetForPartition(
278 maplit::btreeset! {("orders".to_string(), 0), ("orders".to_string(), 1)},
279 ),
280 "undefined offset with no reset policy for partitions: [orders-0, orders-1]",
281 ),
282 ] {
283 assert2::assert!(error.to_string() == expected);
284 }
285 }
286}