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 name(self) -> &'static str {
194 match self {
195 Self::NotLeader => "not leader",
196 Self::LeaderUnknown => "leader unknown",
197 Self::StaleMetadata => "stale metadata",
198 Self::ReplicaUnavailable => "replica unavailable",
199 Self::QuorumUnavailable => "quorum unavailable",
200 Self::TransactionConflict => "transaction conflict",
201 Self::TransactionAborted => "transaction aborted",
202 Self::SerializationFailure => "serialization failure",
203 Self::Deadlock => "deadlock",
204 Self::DeadlineExceeded => "deadline exceeded",
205 Self::Cancelled => "cancelled",
206 Self::CommitOutcomeUnknown => "commit outcome unknown",
207 Self::CommitTooLate => "commit too late",
208 Self::TabletMoved => "tablet moved",
209 Self::TabletSplitting => "tablet splitting",
210 Self::SchemaVersionMismatch => "schema version mismatch",
211 Self::ClusterVersionMismatch => "cluster version mismatch",
212 Self::ResourceExhausted => "resource exhausted",
213 Self::Unauthenticated => "unauthenticated",
214 Self::PermissionDenied => "permission denied",
215 }
216 }
217
218 pub const fn from_code(code: u32) -> Option<Self> {
225 match code {
226 1 => Some(Self::NotLeader),
227 2 => Some(Self::LeaderUnknown),
228 3 => Some(Self::StaleMetadata),
229 4 => Some(Self::ReplicaUnavailable),
230 5 => Some(Self::QuorumUnavailable),
231 6 => Some(Self::TransactionConflict),
232 7 => Some(Self::TransactionAborted),
233 8 => Some(Self::SerializationFailure),
234 9 => Some(Self::Deadlock),
235 10 => Some(Self::DeadlineExceeded),
236 11 => Some(Self::Cancelled),
237 12 => Some(Self::CommitOutcomeUnknown),
238 13 => Some(Self::CommitTooLate),
239 14 => Some(Self::TabletMoved),
240 15 => Some(Self::TabletSplitting),
241 16 => Some(Self::SchemaVersionMismatch),
242 17 => Some(Self::ClusterVersionMismatch),
243 18 => Some(Self::ResourceExhausted),
244 19 => Some(Self::Unauthenticated),
245 20 => Some(Self::PermissionDenied),
246 _ => None,
247 }
248 }
249
250 pub const fn retry_class(self) -> RetryClass {
252 match self {
253 Self::NotLeader
254 | Self::LeaderUnknown
255 | Self::StaleMetadata
256 | Self::TabletMoved
257 | Self::TabletSplitting
258 | Self::SchemaVersionMismatch => RetryClass::AfterMetadataRefresh,
259 Self::ReplicaUnavailable | Self::QuorumUnavailable | Self::ResourceExhausted => {
260 RetryClass::AfterBackoff
261 }
262 Self::TransactionConflict
263 | Self::TransactionAborted
264 | Self::SerializationFailure
265 | Self::Deadlock
266 | Self::CommitTooLate => RetryClass::RetryTransaction,
267 Self::DeadlineExceeded
268 | Self::Cancelled
269 | Self::ClusterVersionMismatch
270 | Self::Unauthenticated
271 | Self::PermissionDenied => RetryClass::Never,
272 Self::CommitOutcomeUnknown => RetryClass::IdempotencyKeyRequired,
273 }
274 }
275
276 pub const fn is_retryable(self) -> bool {
283 matches!(
284 self.retry_class(),
285 RetryClass::AfterMetadataRefresh
286 | RetryClass::AfterBackoff
287 | RetryClass::RetryTransaction
288 )
289 }
290}
291
292#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, thiserror::Error)]
300#[error("{category}: {message}")]
301pub struct CategoryError {
302 pub category: ErrorCategory,
304 pub message: String,
306}
307
308impl CategoryError {
309 pub fn new(category: ErrorCategory, message: impl Into<String>) -> Self {
311 Self {
312 category,
313 message: message.into(),
314 }
315 }
316
317 pub fn code(&self) -> u32 {
320 self.category.code()
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use serde::de::value::{Error as ValueError, StrDeserializer};
328 use serde::de::{
329 DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, VariantAccess, Visitor,
330 };
331 use serde::ser::{Impossible, SerializeStruct};
332 use serde::{Deserialize, Deserializer, Serialize, Serializer};
333
334 #[derive(Debug, Clone, PartialEq, Eq)]
339 enum Value {
340 Str(String),
341 UnitVariant(&'static str),
342 Struct(Vec<(&'static str, Value)>),
343 }
344
345 struct ValueSerializer;
346
347 macro_rules! serialize_unsupported {
348 ($($method:ident ( $($param:ident : $ty:ty),* ) -> $ret:ty ;)*) => {
349 $(
350 fn $method(self, $($param: $ty),*) -> Result<$ret, ValueError> {
351 Err(serde::ser::Error::custom(concat!(
352 stringify!($method),
353 " is not supported by the in-test serde format"
354 )))
355 }
356 )*
357 };
358 }
359
360 impl Serializer for ValueSerializer {
361 type Ok = Value;
362 type Error = ValueError;
363 type SerializeSeq = Impossible<Value, ValueError>;
364 type SerializeTuple = Impossible<Value, ValueError>;
365 type SerializeTupleStruct = Impossible<Value, ValueError>;
366 type SerializeTupleVariant = Impossible<Value, ValueError>;
367 type SerializeMap = Impossible<Value, ValueError>;
368 type SerializeStruct = StructSerializer;
369 type SerializeStructVariant = Impossible<Value, ValueError>;
370
371 fn serialize_str(self, v: &str) -> Result<Value, ValueError> {
372 Ok(Value::Str(v.to_owned()))
373 }
374
375 fn serialize_unit_variant(
376 self,
377 _name: &'static str,
378 _variant_index: u32,
379 variant: &'static str,
380 ) -> Result<Value, ValueError> {
381 Ok(Value::UnitVariant(variant))
382 }
383
384 fn serialize_struct(
385 self,
386 _name: &'static str,
387 len: usize,
388 ) -> Result<StructSerializer, ValueError> {
389 Ok(StructSerializer {
390 fields: Vec::with_capacity(len),
391 })
392 }
393
394 fn serialize_none(self) -> Result<Value, ValueError> {
395 Err(serde::ser::Error::custom(
396 "serialize_none is not supported by the in-test serde format",
397 ))
398 }
399
400 fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Value, ValueError> {
401 Err(serde::ser::Error::custom(
402 "serialize_some is not supported by the in-test serde format",
403 ))
404 }
405
406 fn serialize_newtype_struct<T: ?Sized + Serialize>(
407 self,
408 _name: &'static str,
409 _value: &T,
410 ) -> Result<Value, ValueError> {
411 Err(serde::ser::Error::custom(
412 "serialize_newtype_struct is not supported by the in-test serde format",
413 ))
414 }
415
416 fn serialize_newtype_variant<T: ?Sized + Serialize>(
417 self,
418 _name: &'static str,
419 _variant_index: u32,
420 _variant: &'static str,
421 _value: &T,
422 ) -> Result<Value, ValueError> {
423 Err(serde::ser::Error::custom(
424 "serialize_newtype_variant is not supported by the in-test serde format",
425 ))
426 }
427
428 serialize_unsupported! {
429 serialize_bool(_v: bool) -> Value;
430 serialize_i8(_v: i8) -> Value;
431 serialize_i16(_v: i16) -> Value;
432 serialize_i32(_v: i32) -> Value;
433 serialize_i64(_v: i64) -> Value;
434 serialize_u8(_v: u8) -> Value;
435 serialize_u16(_v: u16) -> Value;
436 serialize_u32(_v: u32) -> Value;
437 serialize_u64(_v: u64) -> Value;
438 serialize_f32(_v: f32) -> Value;
439 serialize_f64(_v: f64) -> Value;
440 serialize_char(_v: char) -> Value;
441 serialize_bytes(_v: &[u8]) -> Value;
442 serialize_unit() -> Value;
443 serialize_unit_struct(_name: &'static str) -> Value;
444 serialize_seq(_len: Option<usize>) -> Self::SerializeSeq;
445 serialize_tuple(_len: usize) -> Self::SerializeTuple;
446 serialize_tuple_struct(_name: &'static str, _len: usize) -> Self::SerializeTupleStruct;
447 serialize_tuple_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Self::SerializeTupleVariant;
448 serialize_map(_len: Option<usize>) -> Self::SerializeMap;
449 serialize_struct_variant(_name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Self::SerializeStructVariant;
450 }
451 }
452
453 struct StructSerializer {
454 fields: Vec<(&'static str, Value)>,
455 }
456
457 impl SerializeStruct for StructSerializer {
458 type Ok = Value;
459 type Error = ValueError;
460
461 fn serialize_field<T: ?Sized + Serialize>(
462 &mut self,
463 key: &'static str,
464 value: &T,
465 ) -> Result<(), ValueError> {
466 self.fields.push((key, value.serialize(ValueSerializer)?));
467 Ok(())
468 }
469
470 fn end(self) -> Result<Value, ValueError> {
471 Ok(Value::Struct(self.fields))
472 }
473 }
474
475 struct ValueDeserializer(Value);
476
477 impl<'de> Deserializer<'de> for ValueDeserializer {
478 type Error = ValueError;
479
480 fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
481 match self.0 {
482 Value::Str(text) => visitor.visit_string(text),
483 Value::UnitVariant(variant) => visitor.visit_enum(UnitVariantAccess { variant }),
484 Value::Struct(fields) => visitor.visit_map(StructAccess {
485 iter: fields.into_iter(),
486 value: None,
487 }),
488 }
489 }
490
491 fn deserialize_struct<V: Visitor<'de>>(
492 self,
493 _name: &'static str,
494 _fields: &'static [&'static str],
495 visitor: V,
496 ) -> Result<V::Value, ValueError> {
497 self.deserialize_any(visitor)
498 }
499
500 fn deserialize_enum<V: Visitor<'de>>(
501 self,
502 _name: &'static str,
503 _variants: &'static [&'static str],
504 visitor: V,
505 ) -> Result<V::Value, ValueError> {
506 self.deserialize_any(visitor)
507 }
508
509 fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
510 self.deserialize_any(visitor)
511 }
512
513 fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, ValueError> {
514 self.deserialize_any(visitor)
515 }
516
517 serde::forward_to_deserialize_any! {
518 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char bytes
519 byte_buf option unit unit_struct newtype_struct seq tuple
520 tuple_struct map identifier ignored_any
521 }
522 }
523
524 struct StructAccess {
525 iter: std::vec::IntoIter<(&'static str, Value)>,
526 value: Option<Value>,
527 }
528
529 impl<'de> MapAccess<'de> for StructAccess {
530 type Error = ValueError;
531
532 fn next_key_seed<K: DeserializeSeed<'de>>(
533 &mut self,
534 seed: K,
535 ) -> Result<Option<K::Value>, ValueError> {
536 match self.iter.next() {
537 Some((key, value)) => {
538 self.value = Some(value);
539 let deserializer: StrDeserializer<'de, ValueError> = key.into_deserializer();
540 seed.deserialize(deserializer).map(Some)
541 }
542 None => Ok(None),
543 }
544 }
545
546 fn next_value_seed<V: DeserializeSeed<'de>>(
547 &mut self,
548 seed: V,
549 ) -> Result<V::Value, ValueError> {
550 let value = self
551 .value
552 .take()
553 .ok_or_else(|| serde::de::Error::custom("struct field value missing"))?;
554 seed.deserialize(ValueDeserializer(value))
555 }
556 }
557
558 struct UnitVariantAccess {
559 variant: &'static str,
560 }
561
562 impl<'de> EnumAccess<'de> for UnitVariantAccess {
563 type Error = ValueError;
564 type Variant = Self;
565
566 fn variant_seed<V: DeserializeSeed<'de>>(
567 self,
568 seed: V,
569 ) -> Result<(V::Value, Self), ValueError> {
570 let deserializer: StrDeserializer<'de, ValueError> = self.variant.into_deserializer();
571 let value = seed.deserialize(deserializer)?;
572 Ok((value, self))
573 }
574 }
575
576 impl<'de> VariantAccess<'de> for UnitVariantAccess {
577 type Error = ValueError;
578
579 fn unit_variant(self) -> Result<(), ValueError> {
580 Ok(())
581 }
582
583 fn newtype_variant_seed<T: DeserializeSeed<'de>>(
584 self,
585 _seed: T,
586 ) -> Result<T::Value, ValueError> {
587 Err(serde::de::Error::custom("expected a unit variant"))
588 }
589
590 fn tuple_variant<V: Visitor<'de>>(
591 self,
592 _len: usize,
593 _visitor: V,
594 ) -> Result<V::Value, ValueError> {
595 Err(serde::de::Error::custom("expected a unit variant"))
596 }
597
598 fn struct_variant<V: Visitor<'de>>(
599 self,
600 _fields: &'static [&'static str],
601 _visitor: V,
602 ) -> Result<V::Value, ValueError> {
603 Err(serde::de::Error::custom("expected a unit variant"))
604 }
605 }
606
607 fn to_value<T: Serialize>(value: &T) -> Value {
608 value
609 .serialize(ValueSerializer)
610 .expect("test serializer failed")
611 }
612
613 fn from_value<T: for<'de> Deserialize<'de>>(value: Value) -> T {
614 T::deserialize(ValueDeserializer(value)).expect("test deserializer failed")
615 }
616
617 #[test]
618 fn codes_are_stable_unique_and_round_trippable() {
619 assert_eq!(ErrorCategory::ALL.len(), 20);
620 let codes: Vec<u32> = ErrorCategory::ALL
621 .iter()
622 .map(|category| category.code())
623 .collect();
624 assert_eq!(codes, (1..=20).collect::<Vec<u32>>());
626 for category in ErrorCategory::ALL {
627 assert_eq!(ErrorCategory::from_code(category.code()), Some(category));
628 }
629 assert_eq!(ErrorCategory::from_code(0), None);
630 assert_eq!(ErrorCategory::from_code(21), None);
631 }
632
633 #[test]
634 fn serde_round_trip_preserves_every_category() {
635 for category in ErrorCategory::ALL {
636 let value = to_value(&category);
637 match &value {
640 Value::UnitVariant(name) => assert_eq!(*name, format!("{category:?}").as_str()),
641 other => panic!("expected unit variant, got {other:?}"),
642 }
643 assert_eq!(from_value::<ErrorCategory>(value), category);
644 }
645 }
646
647 #[test]
648 fn category_error_serde_round_trip() {
649 let original = CategoryError::new(
650 ErrorCategory::CommitOutcomeUnknown,
651 "commit epoch 42 lost contact after propose",
652 );
653 let value = to_value(&original);
654 match &value {
655 Value::Struct(fields) => {
656 assert_eq!(fields.len(), 2);
657 assert_eq!(fields[0].0, "category");
658 assert_eq!(fields[1].0, "message");
659 }
660 other => panic!("expected struct, got {other:?}"),
661 }
662 assert_eq!(from_value::<CategoryError>(value), original);
663 }
664
665 #[test]
666 fn display_is_human_readable() {
667 assert_eq!(ErrorCategory::NotLeader.to_string(), "not leader");
668 assert_eq!(
669 ErrorCategory::CommitOutcomeUnknown.to_string(),
670 "commit outcome unknown"
671 );
672 assert_eq!(
673 ErrorCategory::PermissionDenied.to_string(),
674 "permission denied"
675 );
676 let error = CategoryError::new(
677 ErrorCategory::PermissionDenied,
678 "principal \"alice\" lacks Admin",
679 );
680 assert_eq!(
681 error.to_string(),
682 "permission denied: principal \"alice\" lacks Admin"
683 );
684 assert_eq!(error.code(), ErrorCategory::PermissionDenied.code());
685 }
686
687 #[test]
688 fn name_matches_display_for_every_category() {
689 for category in ErrorCategory::ALL {
690 assert_eq!(category.name(), category.to_string());
691 assert!(!category.name().is_empty());
692 assert!(category.name().len() < 32, "{category:?}");
694 }
695 }
696
697 #[test]
698 fn retry_classification_matches_spec_11_7() {
699 assert!(ErrorCategory::NotLeader.is_retryable());
701 assert_eq!(
702 ErrorCategory::NotLeader.retry_class(),
703 RetryClass::AfterMetadataRefresh
704 );
705 assert!(ErrorCategory::StaleMetadata.is_retryable());
706 assert_eq!(
707 ErrorCategory::StaleMetadata.retry_class(),
708 RetryClass::AfterMetadataRefresh
709 );
710 assert_eq!(
711 ErrorCategory::TabletMoved.retry_class(),
712 RetryClass::AfterMetadataRefresh
713 );
714 assert!(ErrorCategory::ReplicaUnavailable.is_retryable());
716 assert_eq!(
717 ErrorCategory::QuorumUnavailable.retry_class(),
718 RetryClass::AfterBackoff
719 );
720 assert_eq!(
721 ErrorCategory::ResourceExhausted.retry_class(),
722 RetryClass::AfterBackoff
723 );
724 assert!(ErrorCategory::TransactionConflict.is_retryable());
726 assert_eq!(
727 ErrorCategory::Deadlock.retry_class(),
728 RetryClass::RetryTransaction
729 );
730 assert_eq!(
731 ErrorCategory::SerializationFailure.retry_class(),
732 RetryClass::RetryTransaction
733 );
734 assert!(!ErrorCategory::CommitOutcomeUnknown.is_retryable());
737 assert_eq!(
738 ErrorCategory::CommitOutcomeUnknown.retry_class(),
739 RetryClass::IdempotencyKeyRequired
740 );
741 for category in [
743 ErrorCategory::DeadlineExceeded,
744 ErrorCategory::Cancelled,
745 ErrorCategory::ClusterVersionMismatch,
746 ErrorCategory::Unauthenticated,
747 ErrorCategory::PermissionDenied,
748 ] {
749 assert!(
750 !category.is_retryable(),
751 "{category:?} must not be retryable"
752 );
753 assert_eq!(category.retry_class(), RetryClass::Never);
754 }
755 }
756}