Skip to main content

sierradb_client/
error.rs

1use redis::RetryMethod;
2use sierradb_protocol::ErrorCode;
3use std::{fmt, str::FromStr};
4
5/// Errors that can occur when using the SierraDB client.
6#[derive(Debug)]
7pub enum SierraError {
8    /// A protocol-level error returned by the Sierra server
9    Protocol {
10        code: ErrorCode,
11        message: Option<String>,
12    },
13    /// A Redis client error (connection, parsing, etc.)
14    Redis(redis::RedisError),
15}
16
17impl SierraError {
18    /// Parse a Redis error response into a SierraError.
19    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    /// Check if this error indicates a temporary failure that should be
33    /// retried.
34    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    /// Check if this error indicates a cluster availability issue.
42    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    /// Check if this error indicates a client argument error.
50    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    /// Get the error code if this is a protocol error.
58    pub fn error_code(&self) -> Option<ErrorCode> {
59        match self {
60            SierraError::Protocol { code, .. } => Some(*code),
61            _ => None,
62        }
63    }
64
65    /// Get a suggested retry delay in milliseconds for retryable errors.
66    /// Returns None if the error should not be retried.
67    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), // Short delay for temporary failures
75                ErrorCode::Timeout => Some(1000), // Longer delay for timeouts
76                ErrorCode::CircuitOpen => Some(5000), // Wait for circuit breaker
77                ErrorCode::ActorDown | ErrorCode::ActorStopped => Some(500), // Actor restart time
78                ErrorCode::MailboxFull => Some(200), // Mailbox drain time
79                _ => Some(1000),                  // Default retry delay
80            },
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
110/// Result type for Sierra client operations.
111pub 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        // Test parsing Sierra protocol errors directly
120        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}