1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
21pub enum ErrorCode {
22 #[default]
24 Success = 0,
25
26 SyntaxError = 100,
29 ParseError = 101,
31 InvalidStatement = 102,
33 MissingParameter = 103,
35
36 ExecutionError = 200,
39 Timeout = 201,
41 ResourceExhausted = 202,
43 Conflict = 203,
45 Deadlock = 204,
47
48 ValidationError = 300,
51 TypeError = 301,
53 InvalidInput = 302,
55 ConstraintViolation = 303,
57
58 PermissionDenied = 400,
61 Unauthorized = 401,
63 Forbidden = 403,
65
66 ResourceNotFound = 500,
69 ResourceAlreadyExists = 501,
71 ResourceUnavailable = 502,
73
74 InternalError = 900,
77 ServiceUnavailable = 901,
79 Unknown = 999,
81}
82
83impl ErrorCode {
84 pub fn as_i32(&self) -> i32 {
86 *self as i32
87 }
88
89 pub fn from_i32(code: i32) -> Option<Self> {
91 match code {
92 0 => Some(ErrorCode::Success),
93 100 => Some(ErrorCode::SyntaxError),
94 101 => Some(ErrorCode::ParseError),
95 102 => Some(ErrorCode::InvalidStatement),
96 103 => Some(ErrorCode::MissingParameter),
97 200 => Some(ErrorCode::ExecutionError),
98 201 => Some(ErrorCode::Timeout),
99 202 => Some(ErrorCode::ResourceExhausted),
100 203 => Some(ErrorCode::Conflict),
101 204 => Some(ErrorCode::Deadlock),
102 300 => Some(ErrorCode::ValidationError),
103 301 => Some(ErrorCode::TypeError),
104 302 => Some(ErrorCode::InvalidInput),
105 303 => Some(ErrorCode::ConstraintViolation),
106 400 => Some(ErrorCode::PermissionDenied),
107 401 => Some(ErrorCode::Unauthorized),
108 403 => Some(ErrorCode::Forbidden),
109 500 => Some(ErrorCode::ResourceNotFound),
110 501 => Some(ErrorCode::ResourceAlreadyExists),
111 502 => Some(ErrorCode::ResourceUnavailable),
112 900 => Some(ErrorCode::InternalError),
113 901 => Some(ErrorCode::ServiceUnavailable),
114 999 => Some(ErrorCode::Unknown),
115 _ => None,
116 }
117 }
118
119 pub fn category(&self) -> ErrorCategory {
121 match self.as_i32() {
122 0 => ErrorCategory::Success,
123 100..=199 => ErrorCategory::Syntax,
124 200..=299 => ErrorCategory::Execution,
125 300..=399 => ErrorCategory::Validation,
126 400..=499 => ErrorCategory::Permission,
127 500..=599 => ErrorCategory::Resource,
128 900..=999 => ErrorCategory::System,
129 _ => ErrorCategory::Unknown,
130 }
131 }
132
133 pub fn default_message(&self) -> &'static str {
135 match self {
136 ErrorCode::Success => "success",
137 ErrorCode::SyntaxError => "syntax error",
138 ErrorCode::ParseError => "parse error",
139 ErrorCode::InvalidStatement => "invalid statement",
140 ErrorCode::MissingParameter => "missing required parameter",
141 ErrorCode::ExecutionError => "execution error",
142 ErrorCode::Timeout => "execution timeout",
143 ErrorCode::ResourceExhausted => "resource exhausted",
144 ErrorCode::Conflict => "conflict",
145 ErrorCode::Deadlock => "deadlock detected",
146 ErrorCode::ValidationError => "validation error",
147 ErrorCode::TypeError => "type error",
148 ErrorCode::InvalidInput => "invalid input",
149 ErrorCode::ConstraintViolation => "constraint violation",
150 ErrorCode::PermissionDenied => "permission denied",
151 ErrorCode::Unauthorized => "unauthorized",
152 ErrorCode::Forbidden => "forbidden",
153 ErrorCode::ResourceNotFound => "resource not found",
154 ErrorCode::ResourceAlreadyExists => "resource already exists",
155 ErrorCode::ResourceUnavailable => "resource unavailable",
156 ErrorCode::InternalError => "internal server error",
157 ErrorCode::ServiceUnavailable => "service unavailable",
158 ErrorCode::Unknown => "unknown error",
159 }
160 }
161
162 pub fn is_success(&self) -> bool {
164 matches!(self, ErrorCode::Success)
165 }
166
167 pub fn is_client_error(&self) -> bool {
169 let code = self.as_i32();
170 (100..=499).contains(&code)
171 }
172
173 pub fn is_server_error(&self) -> bool {
175 let code = self.as_i32();
176 (500..=599).contains(&code) || (900..=999).contains(&code)
177 }
178
179 pub fn is_retryable(&self) -> bool {
181 matches!(
182 self,
183 ErrorCode::Timeout
184 | ErrorCode::Conflict
185 | ErrorCode::Deadlock
186 | ErrorCode::ResourceExhausted
187 | ErrorCode::ServiceUnavailable
188 )
189 }
190}
191
192impl std::fmt::Display for ErrorCode {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "{} ({})", self.as_i32(), self.default_message())
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum ErrorCategory {
201 Success,
202 Syntax,
203 Execution,
204 Validation,
205 Permission,
206 Resource,
207 System,
208 Unknown,
209}
210
211impl ErrorCategory {
212 pub fn to_http_status(&self) -> u16 {
214 match self {
215 ErrorCategory::Success => 200,
216 ErrorCategory::Syntax => 400,
217 ErrorCategory::Execution => 500,
218 ErrorCategory::Validation => 422,
219 ErrorCategory::Permission => 403,
220 ErrorCategory::Resource => 404,
221 ErrorCategory::System => 500,
222 ErrorCategory::Unknown => 500,
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct PublicError {
230 pub code: ErrorCode,
232 pub message: String,
234}
235
236impl PublicError {
237 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
239 Self {
240 code,
241 message: message.into(),
242 }
243 }
244
245 pub fn with_default_message(code: ErrorCode) -> Self {
247 Self {
248 code,
249 message: code.default_message().to_string(),
250 }
251 }
252
253 pub fn success() -> Self {
255 Self {
256 code: ErrorCode::Success,
257 message: "successes".to_string(),
258 }
259 }
260}
261
262pub trait ToPublicError {
266 fn to_public_error(&self) -> PublicError;
268
269 fn to_error_code(&self) -> ErrorCode;
271
272 fn to_public_message(&self) -> String;
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_error_code_as_i32() {
282 assert_eq!(ErrorCode::Success.as_i32(), 0);
283 assert_eq!(ErrorCode::SyntaxError.as_i32(), 100);
284 assert_eq!(ErrorCode::InternalError.as_i32(), 900);
285 }
286
287 #[test]
288 fn test_error_code_from_i32() {
289 assert_eq!(ErrorCode::from_i32(0), Some(ErrorCode::Success));
290 assert_eq!(ErrorCode::from_i32(100), Some(ErrorCode::SyntaxError));
291 assert_eq!(ErrorCode::from_i32(999), Some(ErrorCode::Unknown));
292 assert_eq!(ErrorCode::from_i32(12345), None);
293 }
294
295 #[test]
296 fn test_error_code_category() {
297 assert_eq!(ErrorCode::Success.category(), ErrorCategory::Success);
298 assert_eq!(ErrorCode::SyntaxError.category(), ErrorCategory::Syntax);
299 assert_eq!(
300 ErrorCode::ExecutionError.category(),
301 ErrorCategory::Execution
302 );
303 assert_eq!(ErrorCode::InternalError.category(), ErrorCategory::System);
304 }
305
306 #[test]
307 fn test_error_code_is_success() {
308 assert!(ErrorCode::Success.is_success());
309 assert!(!ErrorCode::SyntaxError.is_success());
310 assert!(!ErrorCode::InternalError.is_success());
311 }
312
313 #[test]
314 fn test_error_code_is_retryable() {
315 assert!(ErrorCode::Timeout.is_retryable());
316 assert!(ErrorCode::Conflict.is_retryable());
317 assert!(ErrorCode::Deadlock.is_retryable());
318 assert!(!ErrorCode::SyntaxError.is_retryable());
319 assert!(!ErrorCode::PermissionDenied.is_retryable());
320 }
321
322 #[test]
323 fn test_public_error() {
324 let err = PublicError::new(
325 ErrorCode::ResourceNotFound,
326 "user does not exist".to_string(),
327 );
328 assert_eq!(err.code, ErrorCode::ResourceNotFound);
329 assert_eq!(err.message, "user does not exist");
330
331 let default_err = PublicError::with_default_message(ErrorCode::Timeout);
332 assert_eq!(default_err.code, ErrorCode::Timeout);
333 assert_eq!(default_err.message, "execution timeout");
334 }
335}