sierradb_protocol/error.rs
1use std::fmt;
2
3use strum::{AsRefStr, Display, EnumString, IntoStaticStr};
4
5/// Error codes used in Sierra server responses.
6///
7/// These codes follow Redis conventions where applicable, with Sierra-specific
8/// extensions for domain-specific error conditions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, AsRefStr, Display, EnumString, IntoStaticStr)]
10#[strum(serialize_all = "UPPERCASE")]
11pub enum ErrorCode {
12 // Redis-standard error codes
13 /// Cluster is down or partitions unavailable
14 ClusterDown,
15 /// Temporary failure, client should retry
16 TryAgain,
17 /// Request timed out
18 Timeout,
19 /// Invalid syntax in command arguments
20 Syntax,
21 /// Invalid argument provided
22 InvalidArg,
23 /// Resource not found
24 NotFound,
25
26 // Sierra-specific error codes
27 /// Too many request forwards (loop prevention)
28 MaxForwards,
29 /// Circuit breaker is open
30 CircuitOpen,
31 /// Confirmation operation failed
32 ConfirmFailed,
33 /// Replication confirmation failed
34 ReplConfirmFailed,
35 /// System clock error
36 ClockErr,
37 /// Read operation error
38 ReadErr,
39 /// Write operation error
40 WriteErr,
41 /// Remote communication error
42 RemoteErr,
43 /// Network noise protocol error
44 NoiseErr,
45 /// Transport layer error
46 TransportErr,
47 /// Swarm initialization error
48 SwarmErr,
49 /// Actor not running
50 ActorDown,
51 /// Actor stopped or died
52 ActorStopped,
53 /// Actor mailbox is full
54 MailboxFull,
55
56 // WriteError-specific codes
57 /// All replicas failed
58 AllReplicasFailed,
59 /// Buffer was evicted
60 BufferEvicted,
61 /// Buffer is full
62 BufferFull,
63 /// Database operation failed
64 DbOpFailed,
65 /// Insufficient healthy replicas
66 InsufficientReplicas,
67 /// Invalid sender
68 InvalidSender,
69 /// Stale write detected
70 StaleWrite,
71 /// Missing expected partition sequence
72 MissingPartSeq,
73 /// Partition not owned by this node
74 PartNotOwned,
75 /// Replication quorum failed
76 QuorumFailed,
77 /// Remote operation failed
78 RemoteOpFailed,
79 /// Sequence conflict detected
80 SeqConflict,
81 /// Wrong expected sequence
82 WrongSeq,
83 /// Wrong expected version
84 WrongVer,
85
86 // Subscription-specific error codes
87 /// Redirect request to another node
88 Redirect,
89}
90
91impl ErrorCode {
92 /// Format this error code with a message.
93 ///
94 /// # Example
95 /// ```
96 /// use sierradb_protocol::ErrorCode;
97 ///
98 /// let error = ErrorCode::InvalidArg.with_message("invalid timestamp format");
99 /// assert_eq!(error, "INVALIDARG invalid timestamp format");
100 /// ```
101 pub fn with_message(&self, message: impl fmt::Display) -> String {
102 format!("{self} {message}")
103 }
104
105 /// Check if this error code indicates a temporary failure that should be
106 /// retried.
107 pub fn is_retryable(&self) -> bool {
108 matches!(
109 self,
110 ErrorCode::TryAgain
111 | ErrorCode::Timeout
112 | ErrorCode::CircuitOpen
113 | ErrorCode::ActorDown
114 | ErrorCode::ActorStopped
115 | ErrorCode::MailboxFull
116 )
117 }
118
119 /// Check if this error code indicates a cluster availability issue.
120 pub fn is_cluster_issue(&self) -> bool {
121 matches!(
122 self,
123 ErrorCode::ClusterDown
124 | ErrorCode::ReadErr
125 | ErrorCode::WriteErr
126 | ErrorCode::RemoteErr
127 | ErrorCode::NoiseErr
128 | ErrorCode::TransportErr
129 | ErrorCode::SwarmErr
130 )
131 }
132
133 /// Check if this error code indicates a client argument error.
134 pub fn is_client_error(&self) -> bool {
135 matches!(self, ErrorCode::Syntax | ErrorCode::InvalidArg)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_error_code_parsing() {
145 assert_eq!(
146 "CLUSTERDOWN".parse::<ErrorCode>().unwrap(),
147 ErrorCode::ClusterDown
148 );
149 assert_eq!(
150 "INVALIDARG".parse::<ErrorCode>().unwrap(),
151 ErrorCode::InvalidArg
152 );
153 assert!("UNKNOWN".parse::<ErrorCode>().is_err());
154 }
155
156 #[test]
157 fn test_error_categories() {
158 assert!(ErrorCode::TryAgain.is_retryable());
159 assert!(!ErrorCode::Syntax.is_retryable());
160
161 assert!(ErrorCode::ClusterDown.is_cluster_issue());
162 assert!(!ErrorCode::InvalidArg.is_cluster_issue());
163
164 assert!(ErrorCode::InvalidArg.is_client_error());
165 assert!(!ErrorCode::ClusterDown.is_client_error());
166 }
167
168 #[test]
169 fn test_with_message() {
170 let error = ErrorCode::InvalidArg.with_message("invalid timestamp format");
171 assert_eq!(error, "INVALIDARG invalid timestamp format");
172 }
173}