1#[derive(
24 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, thiserror::Error,
25)]
26pub enum ErrorCategory {
27 #[error("not leader")]
30 NotLeader,
31 #[error("leader unknown")]
34 LeaderUnknown,
35 #[error("stale metadata")]
38 StaleMetadata,
39 #[error("replica unavailable")]
42 ReplicaUnavailable,
43 #[error("quorum unavailable")]
46 QuorumUnavailable,
47 #[error("transaction conflict")]
50 TransactionConflict,
51 #[error("transaction aborted")]
54 TransactionAborted,
55 #[error("serialization failure")]
58 SerializationFailure,
59 #[error("deadlock")]
62 Deadlock,
63 #[error("deadline exceeded")]
66 DeadlineExceeded,
67 #[error("cancelled")]
70 Cancelled,
71 #[error("commit outcome unknown")]
75 CommitOutcomeUnknown,
76 #[error("commit too late")]
79 CommitTooLate,
80 #[error("tablet moved")]
83 TabletMoved,
84 #[error("tablet splitting")]
87 TabletSplitting,
88 #[error("schema version mismatch")]
91 SchemaVersionMismatch,
92 #[error("cluster version mismatch")]
96 ClusterVersionMismatch,
97 #[error("resource exhausted")]
100 ResourceExhausted,
101 #[error("unauthenticated")]
104 Unauthenticated,
105 #[error("permission denied")]
108 PermissionDenied,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
113pub enum RetryClass {
114 Never,
118 IdempotencyKeyRequired,
122 AfterMetadataRefresh,
125 AfterBackoff,
128 RetryTransaction,
131}
132
133impl ErrorCategory {
134 pub const ALL: [Self; 20] = [
136 Self::NotLeader,
137 Self::LeaderUnknown,
138 Self::StaleMetadata,
139 Self::ReplicaUnavailable,
140 Self::QuorumUnavailable,
141 Self::TransactionConflict,
142 Self::TransactionAborted,
143 Self::SerializationFailure,
144 Self::Deadlock,
145 Self::DeadlineExceeded,
146 Self::Cancelled,
147 Self::CommitOutcomeUnknown,
148 Self::CommitTooLate,
149 Self::TabletMoved,
150 Self::TabletSplitting,
151 Self::SchemaVersionMismatch,
152 Self::ClusterVersionMismatch,
153 Self::ResourceExhausted,
154 Self::Unauthenticated,
155 Self::PermissionDenied,
156 ];
157
158 pub const fn code(self) -> u32 {
164 match self {
165 Self::NotLeader => 1,
166 Self::LeaderUnknown => 2,
167 Self::StaleMetadata => 3,
168 Self::ReplicaUnavailable => 4,
169 Self::QuorumUnavailable => 5,
170 Self::TransactionConflict => 6,
171 Self::TransactionAborted => 7,
172 Self::SerializationFailure => 8,
173 Self::Deadlock => 9,
174 Self::DeadlineExceeded => 10,
175 Self::Cancelled => 11,
176 Self::CommitOutcomeUnknown => 12,
177 Self::CommitTooLate => 13,
178 Self::TabletMoved => 14,
179 Self::TabletSplitting => 15,
180 Self::SchemaVersionMismatch => 16,
181 Self::ClusterVersionMismatch => 17,
182 Self::ResourceExhausted => 18,
183 Self::Unauthenticated => 19,
184 Self::PermissionDenied => 20,
185 }
186 }
187
188 pub const fn from_code(code: u32) -> Option<Self> {
195 match code {
196 1 => Some(Self::NotLeader),
197 2 => Some(Self::LeaderUnknown),
198 3 => Some(Self::StaleMetadata),
199 4 => Some(Self::ReplicaUnavailable),
200 5 => Some(Self::QuorumUnavailable),
201 6 => Some(Self::TransactionConflict),
202 7 => Some(Self::TransactionAborted),
203 8 => Some(Self::SerializationFailure),
204 9 => Some(Self::Deadlock),
205 10 => Some(Self::DeadlineExceeded),
206 11 => Some(Self::Cancelled),
207 12 => Some(Self::CommitOutcomeUnknown),
208 13 => Some(Self::CommitTooLate),
209 14 => Some(Self::TabletMoved),
210 15 => Some(Self::TabletSplitting),
211 16 => Some(Self::SchemaVersionMismatch),
212 17 => Some(Self::ClusterVersionMismatch),
213 18 => Some(Self::ResourceExhausted),
214 19 => Some(Self::Unauthenticated),
215 20 => Some(Self::PermissionDenied),
216 _ => None,
217 }
218 }
219
220 pub const fn retry_class(self) -> RetryClass {
222 match self {
223 Self::NotLeader
224 | Self::LeaderUnknown
225 | Self::StaleMetadata
226 | Self::TabletMoved
227 | Self::TabletSplitting
228 | Self::SchemaVersionMismatch => RetryClass::AfterMetadataRefresh,
229 Self::ReplicaUnavailable | Self::QuorumUnavailable | Self::ResourceExhausted => {
230 RetryClass::AfterBackoff
231 }
232 Self::TransactionConflict
233 | Self::TransactionAborted
234 | Self::SerializationFailure
235 | Self::Deadlock
236 | Self::CommitTooLate => RetryClass::RetryTransaction,
237 Self::DeadlineExceeded
238 | Self::Cancelled
239 | Self::ClusterVersionMismatch
240 | Self::Unauthenticated
241 | Self::PermissionDenied => RetryClass::Never,
242 Self::CommitOutcomeUnknown => RetryClass::IdempotencyKeyRequired,
243 }
244 }
245
246 pub const fn is_retryable(self) -> bool {
253 matches!(
254 self.retry_class(),
255 RetryClass::AfterMetadataRefresh
256 | RetryClass::AfterBackoff
257 | RetryClass::RetryTransaction
258 )
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, thiserror::Error)]
270#[error("{category}: {message}")]
271pub struct CategoryError {
272 pub category: ErrorCategory,
274 pub message: String,
276}
277
278impl CategoryError {
279 pub fn new(category: ErrorCategory, message: impl Into<String>) -> Self {
281 Self {
282 category,
283 message: message.into(),
284 }
285 }
286
287 pub fn code(&self) -> u32 {
290 self.category.code()
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use serde::de::value::{Error as ValueError, StrDeserializer};
298 use serde::de::{
299 DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, VariantAccess, Visitor,
300 };
301 use serde::ser::{Impossible, SerializeStruct};
302 use serde::{Deserialize, Deserializer, Serialize, Serializer};
303
304 #[derive(Debug, Clone, PartialEq, Eq)]
309 enum Value {
310 Str(String),
311 UnitVariant(&'static str),
312 Struct(Vec<(&'static str, Value)>),
313 }
314
315 struct ValueSerializer;
316
317 macro_rules! serialize_unsupported {
318 ($($method:ident ( $($param:ident : $ty:ty),* ) -> $ret:ty ;)*) => {
319 $(
320 fn $method(self, $($param: $ty),*) -> Result<$ret, ValueError> {
321 Err(serde::ser::Error::custom(concat!(
322 stringify!($method),
323 " is not supported by the in-test serde format"
324 )))
325 }
326 )*
327 };
328 }
329
330 impl Serializer for ValueSerializer {
331 type Ok = Value;
332 type Error = ValueError;
333 type SerializeSeq = Impossible<Value, ValueError>;
334 type SerializeTuple = Impossible<Value, ValueError>;
335 type SerializeTupleStruct = Impossible<Value, ValueError>;
336 type SerializeTupleVariant = Impossible<Value, ValueError>;
337 type SerializeMap = Impossible<Value, ValueError>;
338 type SerializeStruct = StructSerializer;
339 type SerializeStructVariant = Impossible<Value, ValueError>;
340
341 fn serialize_str(self, v: &str) -> Result<Value, ValueError> {
342 Ok(Value::Str(v.to_owned()))
343 }
344
345 fn serialize_unit_variant(
346 self,
347 _name: &'static str,
348 _variant_index: u32,
349 variant: &'static str,
350 ) -> Result<Value, ValueError> {
351 Ok(Value::UnitVariant(variant))
352 }
353
354 fn serialize_struct(
355 self,
356 _name: &'static str,
357 len: usize,
358 ) -> Result<StructSerializer, ValueError> {
359 Ok(StructSerializer {
360 fields: Vec::with_capacity(len),
361 })
362 }
363
364 fn serialize_none(self) -> Result<Value, ValueError> {
365 Err(serde::ser::Error::custom(
366 "serialize_none is not supported by the in-test serde format",
367 ))
368 }
369
370 fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Value, ValueError> {
371 Err(serde::ser::Error::custom(
372 "serialize_some is not supported by the in-test serde format",
373 ))
374 }
375
376 fn serialize_newtype_struct<T: ?Sized + Serialize>(
377 self,
378 _name: &'static str,
379 _value: &T,
380 ) -> Result<Value, ValueError> {
381 Err(serde::ser::Error::custom(
382 "serialize_newtype_struct is not supported by the in-test serde format",
383 ))
384 }
385
386 fn serialize_newtype_variant<T: ?Sized + Serialize>(
387 self,
388 _name: &'static str,
389 _variant_index: u32,
390 _variant: &'static str,
391 _value: &T,
392 ) -> Result<Value, ValueError> {
393 Err(serde::ser::Error::custom(
394 "serialize_newtype_variant is not supported by the in-test serde format",
395 ))
396 }
397
398 serialize_unsupported! {
399 serialize_bool(_v: bool) -> Value;
400 serialize_i8(_v: i8) -> Value;
401 serialize_i16(_v: i16) -> Value;
402 serialize_i32(_v: i32) -> Value;
403 serialize_i64(_v: i64) -> Value;
404 serialize_u8(_v: u8) -> Value;
405 serialize_u16(_v: u16) -> Value;
406 serialize_u32(_v: u32) -> Value;
407 serialize_u64(_v: u64) -> Value;
408 serialize_f32(_v: f32) -> Value;
409 serialize_f64(_v: f64) -> Value;
410 serialize_char(_v: char) -> Value;
411 serialize_bytes(_v: &[u8]) -> Value;
412 serialize_unit() -> Value;
413 serialize_unit_struct(_name: &'static str) -> Value;
414 serialize_seq(_len: Option<usize>) -> Self::SerializeSeq;
415 serialize_tuple(_len: usize) -> Self::SerializeTuple;
416 serialize_tuple_struct(_name: &'static str, _len: usize) -> Self::SerializeTupleStruct;
417 serialize_tuple_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Self::SerializeTupleVariant;
418 serialize_map(_len: Option<usize>) -> Self::SerializeMap;
419 serialize_struct_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Self::SerializeStructVariant;
420 }
421 }
422
423 struct StructSerializer {
424 fields: Vec<(&'static str, Value)>,
425 }
426
427 impl SerializeStruct for StructSerializer {
428 type Ok = Value;
429 type Error = ValueError;
430
431 fn serialize_field<T: ?Sized + Serialize>(
432 &mut self,
433 key: &'static str,
434 value: &T,
435 ) -> Result<(), ValueError> {
436 self.fields.push((key, value.serialize(ValueSerializer)?));
437 Ok(())
438 }
439
440 fn end(self) -> Result<Value, ValueError> {
441 Ok(Value::Struct(self.fields))
442 }
443 }
444
445 struct ValueDeserializer(Value);
446
447 impl<'de> Deserializer<'de> for ValueDeserializer {
448 type Error = ValueError;
449
450 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
451 match self.0 {
452 Value::Str(text) => visitor.visit_string(text),
453 Value::UnitVariant(variant) => visitor.visit_enum(UnitVariantAccess { variant }),
454 Value::Struct(fields) => visitor.visit_map(StructAccess {
455 iter: fields.into_iter(),
456 value: None,
457 }),
458 }
459 }
460
461 fn deserialize_struct<V: Visitor<'de>>(
462 self,
463 _name: &'static str,
464 _fields: &'static [&'static str],
465 visitor: V,
466 ) -> Result<V::Value, ValueError> {
467 self.deserialize_any(visitor)
468 }
469
470 fn deserialize_enum<V: Visitor<'de>>(
471 self,
472 _name: &'static str,
473 _variants: &'static [&'static str],
474 visitor: V,
475 ) -> Result<V::Value, ValueError> {
476 self.deserialize_any(visitor)
477 }
478
479 fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
480 self.deserialize_any(visitor)
481 }
482
483 fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
484 self.deserialize_any(visitor)
485 }
486
487 serde::forward_to_deserialize_any! {
488 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char bytes
489 byte_buf option unit unit_struct newtype_struct seq tuple
490 tuple_struct map identifier ignored_any
491 }
492 }
493
494 struct StructAccess {
495 iter: std::vec::IntoIter<(&'static str, Value)>,
496 value: Option<Value>,
497 }
498
499 impl<'de> MapAccess<'de> for StructAccess {
500 type Error = ValueError;
501
502 fn next_key_seed<K: DeserializeSeed<'de>>(
503 &mut self,
504 seed: K,
505 ) -> Result<Option<K::Value>, ValueError> {
506 match self.iter.next() {
507 Some((key, value)) => {
508 self.value = Some(value);
509 let deserializer: StrDeserializer<'de, ValueError> = key.into_deserializer();
510 seed.deserialize(deserializer).map(Some)
511 }
512 None => Ok(None),
513 }
514 }
515
516 fn next_value_seed<V: DeserializeSeed<'de>>(
517 &mut self,
518 seed: V,
519 ) -> Result<V::Value, ValueError> {
520 let value = self
521 .value
522 .take()
523 .ok_or_else(|| serde::de::Error::custom("struct field value missing"))?;
524 seed.deserialize(ValueDeserializer(value))
525 }
526 }
527
528 struct UnitVariantAccess {
529 variant: &'static str,
530 }
531
532 impl<'de> EnumAccess<'de> for UnitVariantAccess {
533 type Error = ValueError;
534 type Variant = Self;
535
536 fn variant_seed<V: DeserializeSeed<'de>>(
537 self,
538 seed: V,
539 ) -> Result<(V::Value, Self), ValueError> {
540 let deserializer: StrDeserializer<'de, ValueError> = self.variant.into_deserializer();
541 let value = seed.deserialize(deserializer)?;
542 Ok((value, self))
543 }
544 }
545
546 impl<'de> VariantAccess<'de> for UnitVariantAccess {
547 type Error = ValueError;
548
549 fn unit_variant(self) -> Result<(), ValueError> {
550 Ok(())
551 }
552
553 fn newtype_variant_seed<T: DeserializeSeed<'de>>(
554 self,
555 _seed: T,
556 ) -> Result<T::Value, ValueError> {
557 Err(serde::de::Error::custom("expected a unit variant"))
558 }
559
560 fn tuple_variant<V: Visitor<'de>>(
561 self,
562 _len: usize,
563 _visitor: V,
564 ) -> Result<V::Value, ValueError> {
565 Err(serde::de::Error::custom("expected a unit variant"))
566 }
567
568 fn struct_variant<V: Visitor<'de>>(
569 self,
570 _fields: &'static [&'static str],
571 _visitor: V,
572 ) -> Result<V::Value, ValueError> {
573 Err(serde::de::Error::custom("expected a unit variant"))
574 }
575 }
576
577 fn to_value<T: Serialize>(value: &T) -> Value {
578 value
579 .serialize(ValueSerializer)
580 .expect("test serializer failed")
581 }
582
583 fn from_value<T: for<'de> Deserialize<'de>>(value: Value) -> T {
584 T::deserialize(ValueDeserializer(value)).expect("test deserializer failed")
585 }
586
587 #[test]
588 fn codes_are_stable_unique_and_round_trippable() {
589 assert_eq!(ErrorCategory::ALL.len(), 20);
590 let codes: Vec<u32> = ErrorCategory::ALL
591 .iter()
592 .map(|category| category.code())
593 .collect();
594 assert_eq!(codes, (1..=20).collect::<Vec<u32>>());
596 for category in ErrorCategory::ALL {
597 assert_eq!(ErrorCategory::from_code(category.code()), Some(category));
598 }
599 assert_eq!(ErrorCategory::from_code(0), None);
600 assert_eq!(ErrorCategory::from_code(21), None);
601 }
602
603 #[test]
604 fn serde_round_trip_preserves_every_category() {
605 for category in ErrorCategory::ALL {
606 let value = to_value(&category);
607 match &value {
610 Value::UnitVariant(name) => assert_eq!(*name, format!("{category:?}").as_str()),
611 other => panic!("expected unit variant, got {other:?}"),
612 }
613 assert_eq!(from_value::<ErrorCategory>(value), category);
614 }
615 }
616
617 #[test]
618 fn category_error_serde_round_trip() {
619 let original = CategoryError::new(
620 ErrorCategory::CommitOutcomeUnknown,
621 "commit epoch 42 lost contact after propose",
622 );
623 let value = to_value(&original);
624 match &value {
625 Value::Struct(fields) => {
626 assert_eq!(fields.len(), 2);
627 assert_eq!(fields[0].0, "category");
628 assert_eq!(fields[1].0, "message");
629 }
630 other => panic!("expected struct, got {other:?}"),
631 }
632 assert_eq!(from_value::<CategoryError>(value), original);
633 }
634
635 #[test]
636 fn display_is_human_readable() {
637 assert_eq!(ErrorCategory::NotLeader.to_string(), "not leader");
638 assert_eq!(
639 ErrorCategory::CommitOutcomeUnknown.to_string(),
640 "commit outcome unknown"
641 );
642 assert_eq!(
643 ErrorCategory::PermissionDenied.to_string(),
644 "permission denied"
645 );
646 let error = CategoryError::new(
647 ErrorCategory::PermissionDenied,
648 "principal \"alice\" lacks Admin",
649 );
650 assert_eq!(
651 error.to_string(),
652 "permission denied: principal \"alice\" lacks Admin"
653 );
654 assert_eq!(error.code(), ErrorCategory::PermissionDenied.code());
655 }
656
657 #[test]
658 fn retry_classification_matches_spec_11_7() {
659 assert!(ErrorCategory::NotLeader.is_retryable());
661 assert_eq!(
662 ErrorCategory::NotLeader.retry_class(),
663 RetryClass::AfterMetadataRefresh
664 );
665 assert!(ErrorCategory::StaleMetadata.is_retryable());
666 assert_eq!(
667 ErrorCategory::StaleMetadata.retry_class(),
668 RetryClass::AfterMetadataRefresh
669 );
670 assert_eq!(
671 ErrorCategory::TabletMoved.retry_class(),
672 RetryClass::AfterMetadataRefresh
673 );
674 assert!(ErrorCategory::ReplicaUnavailable.is_retryable());
676 assert_eq!(
677 ErrorCategory::QuorumUnavailable.retry_class(),
678 RetryClass::AfterBackoff
679 );
680 assert_eq!(
681 ErrorCategory::ResourceExhausted.retry_class(),
682 RetryClass::AfterBackoff
683 );
684 assert!(ErrorCategory::TransactionConflict.is_retryable());
686 assert_eq!(
687 ErrorCategory::Deadlock.retry_class(),
688 RetryClass::RetryTransaction
689 );
690 assert_eq!(
691 ErrorCategory::SerializationFailure.retry_class(),
692 RetryClass::RetryTransaction
693 );
694 assert!(!ErrorCategory::CommitOutcomeUnknown.is_retryable());
697 assert_eq!(
698 ErrorCategory::CommitOutcomeUnknown.retry_class(),
699 RetryClass::IdempotencyKeyRequired
700 );
701 for category in [
703 ErrorCategory::DeadlineExceeded,
704 ErrorCategory::Cancelled,
705 ErrorCategory::ClusterVersionMismatch,
706 ErrorCategory::Unauthenticated,
707 ErrorCategory::PermissionDenied,
708 ] {
709 assert!(
710 !category.is_retryable(),
711 "{category:?} must not be retryable"
712 );
713 assert_eq!(category.retry_class(), RetryClass::Never);
714 }
715 }
716}