1use redis::RetryMethod;
2use sierradb_protocol::ErrorCode;
3use std::{fmt, str::FromStr};
4
5#[derive(Debug)]
7pub enum SierraError {
8 Protocol {
10 code: ErrorCode,
11 message: Option<String>,
12 },
13 Redis(redis::RedisError),
15}
16
17impl SierraError {
18 pub fn from_redis_error(redis_error: redis::RedisError) -> Self {
20 let code = redis_error
21 .code()
22 .and_then(|code| ErrorCode::from_str(code).ok());
23 match code {
24 Some(code) => SierraError::Protocol {
25 code,
26 message: redis_error.detail().map(ToOwned::to_owned),
27 },
28 None => SierraError::Redis(redis_error),
29 }
30 }
31
32 pub fn is_retryable(&self) -> bool {
35 match self {
36 SierraError::Protocol { code, .. } => code.is_retryable(),
37 SierraError::Redis(err) => !matches!(err.retry_method(), RetryMethod::NoRetry),
38 }
39 }
40
41 pub fn is_cluster_issue(&self) -> bool {
43 match self {
44 SierraError::Protocol { code, .. } => code.is_cluster_issue(),
45 SierraError::Redis(err) => err.is_cluster_error(),
46 }
47 }
48
49 pub fn is_client_error(&self) -> bool {
51 match self {
52 SierraError::Protocol { code, .. } => code.is_client_error(),
53 SierraError::Redis(_) => false,
54 }
55 }
56
57 pub fn error_code(&self) -> Option<ErrorCode> {
59 match self {
60 SierraError::Protocol { code, .. } => Some(*code),
61 _ => None,
62 }
63 }
64
65 pub fn retry_delay_ms(&self) -> Option<u64> {
68 if !self.is_retryable() {
69 return None;
70 }
71
72 match self {
73 SierraError::Protocol { code, .. } => match code {
74 ErrorCode::TryAgain => Some(100), ErrorCode::Timeout => Some(1000), ErrorCode::CircuitOpen => Some(5000), ErrorCode::ActorDown | ErrorCode::ActorStopped => Some(500), ErrorCode::MailboxFull => Some(200), _ => Some(1000), },
81 _ => None,
82 }
83 }
84}
85
86impl fmt::Display for SierraError {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 SierraError::Protocol { code, message } => match message {
90 Some(msg) => {
91 write!(f, "sierra protocol error: {code} {msg}")
92 }
93 None => {
94 write!(f, "sierra protocol error: {code}")
95 }
96 },
97 SierraError::Redis(err) => write!(f, "redis error: {err}"),
98 }
99 }
100}
101
102impl std::error::Error for SierraError {}
103
104impl From<redis::RedisError> for SierraError {
105 fn from(err: redis::RedisError) -> Self {
106 SierraError::from_redis_error(err)
107 }
108}
109
110pub type SierraResult<T> = Result<T, SierraError>;
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_sierra_error_parsing() {
119 let sierra_err = SierraError::Protocol {
121 code: ErrorCode::ClusterDown,
122 message: Some("No available partitions".to_string()),
123 };
124
125 assert_eq!(sierra_err.error_code(), Some(ErrorCode::ClusterDown));
126 assert!(!sierra_err.is_retryable());
127 assert!(sierra_err.is_cluster_issue());
128 }
129
130 #[test]
131 fn test_error_categorization() {
132 let protocol_err = SierraError::Protocol {
133 code: ErrorCode::TryAgain,
134 message: Some("Try again later".to_string()),
135 };
136
137 assert!(protocol_err.is_retryable());
138 assert!(!protocol_err.is_client_error());
139 assert!(!protocol_err.is_cluster_issue());
140 assert_eq!(protocol_err.error_code(), Some(ErrorCode::TryAgain));
141 }
142}