1use std::fmt;
7
8pub type GroveResult<T> = Result<T, GroveError>;
10
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub enum GroveError {
14ExtensionNotFound {
16extension_id:String,
18message:Option<String>,
20},
21
22ExtensionLoadFailed {
24extension_id:String,
26reason:String,
28path:Option<String>,
30},
31
32ActivationFailed {
34extension_id:String,
36reason:String,
38},
39
40DeactivationFailed {
42extension_id:String,
44reason:String,
46},
47
48WASMRuntimeError {
50reason:String,
52module_id:Option<String>,
54},
55
56WASMCompilationFailed {
58reason:String,
60module_path:Option<String>,
62},
63
64WASMModuleNotFound {
66module_id:String,
68},
69
70TransportError {
72transport_type:String,
74reason:String,
76},
77
78ConnectionError {
80endpoint:String,
82reason:String,
84},
85
86APIError {
88api_method:String,
90reason:String,
92error_code:Option<i32>,
94},
95
96ConfigurationError {
98key:String,
100reason:String,
102},
103
104IoError {
106path:Option<String>,
108operation:String,
110reason:String,
112},
113
114SerializationError {
116type_name:String,
118reason:String,
120},
121
122DeserializationError {
124type_name:String,
126reason:String,
128},
129
130Timeout {
132operation:String,
134timeout_ms:u64,
136},
137
138InvalidArgument {
140argument_name:String,
142reason:String,
144},
145
146NotImplemented {
148feature:String,
150},
151
152PermissionDenied {
154resource:String,
156reason:String,
158},
159
160ResourceExhausted {
162resource:String,
164reason:String,
166},
167
168InternalError {
170reason:String,
172#[serde(skip)]
174backtrace:Option<String>,
175},
176}
177
178impl GroveError {
179 pub fn extension_not_found(extension_id:impl Into<String>) -> Self {
181 Self::ExtensionNotFound { extension_id:extension_id.into(), message:None }
182 }
183
184 pub fn extension_load_failed(extension_id:impl Into<String>, reason:impl Into<String>) -> Self {
186 Self::ExtensionLoadFailed { extension_id:extension_id.into(), reason:reason.into(), path:None }
187 }
188
189 pub fn activation_failed(extension_id:impl Into<String>, reason:impl Into<String>) -> Self {
191 Self::ActivationFailed { extension_id:extension_id.into(), reason:reason.into() }
192 }
193
194 pub fn wasm_runtime_error(reason:impl Into<String>) -> Self {
196 Self::WASMRuntimeError { reason:reason.into(), module_id:None }
197 }
198
199 pub fn transport_error(transport_type:impl Into<String>, reason:impl Into<String>) -> Self {
201 Self::TransportError { transport_type:transport_type.into(), reason:reason.into() }
202 }
203
204 pub fn connection_error(endpoint:impl Into<String>, reason:impl Into<String>) -> Self {
206 Self::ConnectionError { endpoint:endpoint.into(), reason:reason.into() }
207 }
208
209 pub fn api_error(api_method:impl Into<String>, reason:impl Into<String>) -> Self {
211 Self::APIError { api_method:api_method.into(), reason:reason.into(), error_code:None }
212 }
213
214 pub fn timeout(operation:impl Into<String>, timeout_ms:u64) -> Self {
216 Self::Timeout { operation:operation.into(), timeout_ms }
217 }
218
219 pub fn invalid_argument(argument_name:impl Into<String>, reason:impl Into<String>) -> Self {
221 Self::InvalidArgument { argument_name:argument_name.into(), reason:reason.into() }
222 }
223
224 pub fn not_implemented(feature:impl Into<String>) -> Self { Self::NotImplemented { feature:feature.into() } }
226
227 pub fn error_code(&self) -> &'static str {
229 match self {
230 Self::ExtensionNotFound { .. } => "EXT_NOT_FOUND",
231 Self::ExtensionLoadFailed { .. } => "EXT_LOAD_FAILED",
232 Self::ActivationFailed { .. } => "ACTIVATION_FAILED",
233 Self::DeactivationFailed { .. } => "DEACTIVATION_FAILED",
234 Self::WASMRuntimeError { .. } => "WASM_RUNTIME_ERROR",
235 Self::WASMCompilationFailed { .. } => "WASM_COMPILATION_FAILED",
236 Self::WASMModuleNotFound { .. } => "WASM_MODULE_NOT_FOUND",
237 Self::TransportError { .. } => "TRANSPORT_ERROR",
238 Self::ConnectionError { .. } => "CONNECTION_ERROR",
239 Self::APIError { .. } => "API_ERROR",
240 Self::ConfigurationError { .. } => "CONFIGURATION_ERROR",
241 Self::IoError { .. } => "IO_ERROR",
242 Self::SerializationError { .. } => "SERIALIZATION_ERROR",
243 Self::DeserializationError { .. } => "DESERIALIZATION_ERROR",
244 Self::Timeout { .. } => "TIMEOUT",
245 Self::InvalidArgument { .. } => "INVALID_ARGUMENT",
246 Self::NotImplemented { .. } => "NOT_IMPLEMENTED",
247 Self::PermissionDenied { .. } => "PERMISSION_DENIED",
248 Self::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED",
249 Self::InternalError { .. } => "INTERNAL_ERROR",
250 }
251 }
252
253 pub fn is_recoverable(&self) -> bool {
255 matches!(
256 self,
257 Self::Timeout { .. }
258 | Self::TransportError { .. }
259 | Self::ConnectionError { .. }
260 | Self::ResourceExhausted { .. }
261 )
262 }
263
264 pub fn is_transient(&self) -> bool {
266 matches!(
267 self,
268 Self::Timeout { .. } | Self::TransportError { .. } | Self::ConnectionError { .. }
269 )
270 }
271}
272
273impl fmt::Display for GroveError {
274 fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
275 match self {
276 Self::ExtensionNotFound { extension_id, message } => {
277 if let Some(msg) = message {
278 write!(f, "Extension not found: {} - {}", extension_id, msg)
279 } else {
280 write!(f, "Extension not found: {}", extension_id)
281 }
282 },
283 Self::ExtensionLoadFailed { extension_id, reason, path } => {
284 if let Some(path) = path {
285 write!(f, "Failed to load extension #{:?}: {} - {}", path, extension_id, reason)
286 } else {
287 write!(f, "Failed to load extension {}: {}", extension_id, reason)
288 }
289 },
290 Self::ActivationFailed { extension_id, reason } => {
291 write!(f, "Activation failed for extension {}: {}", extension_id, reason)
292 },
293 Self::DeactivationFailed { extension_id, reason } => {
294 write!(f, "Deactivation failed for extension {}: {}", extension_id, reason)
295 },
296 Self::WASMRuntimeError { reason, module_id } => {
297 if let Some(id) = module_id {
298 write!(f, "WASM runtime error for module {}: {}", id, reason)
299 } else {
300 write!(f, "WASM runtime error: {}", reason)
301 }
302 },
303 Self::WASMCompilationFailed { reason, module_path } => {
304 if let Some(path) = module_path {
305 write!(f, "WASM compilation failed for {:?}: {}", path, reason)
306 } else {
307 write!(f, "WASM compilation failed: {}", reason)
308 }
309 },
310 Self::WASMModuleNotFound { module_id } => {
311 write!(f, "WASM module not found: {}", module_id)
312 },
313 Self::TransportError { transport_type, reason } => {
314 write!(f, "Transport error ({:?}): {}", transport_type, reason)
315 },
316 Self::ConnectionError { endpoint, reason } => {
317 write!(f, "Connection error to {}: {}", endpoint, reason)
318 },
319 Self::APIError { api_method, reason, .. } => {
320 write!(f, "API error for {}: {}", api_method, reason)
321 },
322 Self::ConfigurationError { key, reason } => {
323 write!(f, "Configuration error for '{}': {}", key, reason)
324 },
325 Self::IoError { operation, reason, .. } => {
326 write!(f, "I/O error for operation '{}': {}", operation, reason)
327 },
328 Self::SerializationError { type_name, reason } => {
329 write!(f, "Serialization error for type '{}': {}", type_name, reason)
330 },
331 Self::DeserializationError { type_name, reason } => {
332 write!(f, "Deserialization error for type '{}': {}", type_name, reason)
333 },
334 Self::Timeout { operation, timeout_ms } => {
335 write!(f, "Timeout after {}ms for operation: {}", timeout_ms, operation)
336 },
337 Self::InvalidArgument { argument_name, reason } => {
338 write!(f, "Invalid argument '{}': {}", argument_name, reason)
339 },
340 Self::NotImplemented { feature } => {
341 write!(f, "Feature not implemented: {}", feature)
342 },
343 Self::PermissionDenied { resource, reason } => {
344 write!(f, "Permission denied for '{}': {}", resource, reason)
345 },
346 Self::ResourceExhausted { resource, reason } => {
347 write!(f, "Resource exhausted '{}': {}", resource, reason)
348 },
349 Self::InternalError { reason, .. } => {
350 write!(f, "Internal error: {}", reason)
351 },
352 }
353 }
354}
355
356impl std::error::Error for GroveError {}
357
358impl From<std::io::Error> for GroveError {
360 fn from(err:std::io::Error) -> Self {
361 Self::IoError { path:None, operation:"unknown".to_string(), reason:err.to_string() }
362 }
363}
364
365impl From<serde_json::Error> for GroveError {
367 fn from(err:serde_json::Error) -> Self {
368 if err.is_io() {
369 Self::IoError { path:None, operation:"serde_json".to_string(), reason:err.to_string() }
370 } else {
371 Self::DeserializationError { type_name:"unknown".to_string(), reason:err.to_string() }
372 }
373 }
374}
375
376pub trait ResultExt<T> {
378 fn map_grove_error(self, context:impl Into<String>) -> GroveResult<T>;
380}
381
382impl<T, E> ResultExt<T> for Result<T, E>
383where
384 E: std::error::Error + Send + Sync + 'static,
385{
386 fn map_grove_error(self, context:impl Into<String>) -> GroveResult<T> {
387 self.map_err(|e| {
388 GroveError::InternalError {
389 reason:format!("{}: {}", context.into(), e),
390 backtrace:std::backtrace::Backtrace::capture().to_string().into(),
391 }
392 })
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_error_creation() {
402 let err = GroveError::extension_not_found("test.ext");
403 assert_eq!(err.error_code(), "EXT_NOT_FOUND");
404 }
405
406 #[test]
407 fn test_error_display() {
408 let err = GroveError::activation_failed("test.ext", "timeout");
409 assert!(err.to_string().contains("test.ext"));
410 assert!(err.to_string().contains("timeout"));
411 }
412
413 #[test]
414 fn test_error_retryable() {
415 let timeout = GroveError::timeout("test", 5000);
416 assert!(timeout.is_transient());
417 assert!(timeout.is_recoverable());
418
419 let not_found = GroveError::extension_not_found("test.ext");
420 assert!(!not_found.is_transient());
421 assert!(!not_found.is_recoverable());
422 }
423
424 #[test]
425 fn test_io_error_conversion() {
426 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
427 let grove_err = GroveError::from(io_err);
428 assert_eq!(grove_err.error_code(), "IO_ERROR");
429 }
430}