Skip to main content

Grove/Common/
Error.rs

1//! Error Types Module
2//!
3//! Defines error types used throughout the Grove codebase.
4//! This module provides a unified error handling approach.
5
6use std::fmt;
7
8/// Grove result type alias
9pub type GroveResult<T> = Result<T, GroveError>;
10
11/// Grove error type
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub enum GroveError {
14	/// Extension not found error
15	ExtensionNotFound {
16		/// The extension identifier
17		extension_id:String,
18
19		/// Optional error message
20		message:Option<String>,
21	},
22
23	/// Extension loading failed error
24	ExtensionLoadFailed {
25		/// The extension identifier
26		extension_id:String,
27
28		/// The failure reason
29		reason:String,
30
31		/// Optional path to the extension
32		path:Option<String>,
33	},
34
35	/// Extension activation failed error
36	ActivationFailed {
37		/// The extension identifier
38		extension_id:String,
39
40		/// The failure reason
41		reason:String,
42	},
43
44	/// Extension deactivation failed error
45	DeactivationFailed {
46		/// The extension identifier
47		extension_id:String,
48
49		/// The failure reason
50		reason:String,
51	},
52
53	/// WASM runtime error
54	WASMRuntimeError {
55		/// The error reason
56		reason:String,
57
58		/// Optional module identifier
59		module_id:Option<String>,
60	},
61
62	/// WASM compilation failed error
63	WASMCompilationFailed {
64		/// The failure reason
65		reason:String,
66
67		/// Optional path to the module
68		module_path:Option<String>,
69	},
70
71	/// WASM module not found error
72	WASMModuleNotFound {
73		/// The module identifier
74		module_id:String,
75	},
76
77	/// Transport error
78	TransportError {
79		/// The transport type
80		transport_type:String,
81
82		/// The error reason
83		reason:String,
84	},
85
86	/// Connection error
87	ConnectionError {
88		/// The endpoint that failed
89		endpoint:String,
90
91		/// The error reason
92		reason:String,
93	},
94
95	/// API call error
96	APIError {
97		/// The API method that failed
98		api_method:String,
99
100		/// The error reason
101		reason:String,
102
103		/// Optional error code
104		error_code:Option<i32>,
105	},
106
107	/// Configuration error
108	ConfigurationError {
109		/// The configuration key
110		key:String,
111
112		/// The error reason
113		reason:String,
114	},
115
116	/// I/O error
117	IoError {
118		/// Optional path related to the error
119		path:Option<String>,
120
121		/// The operation that failed
122		operation:String,
123
124		/// The error reason
125		reason:String,
126	},
127
128	/// Serialization error
129	SerializationError {
130		/// The type name being serialized
131		type_name:String,
132
133		/// The error reason
134		reason:String,
135	},
136
137	/// Deserialization error
138	DeserializationError {
139		/// The type name being deserialized
140		type_name:String,
141
142		/// The error reason
143		reason:String,
144	},
145
146	/// Timeout error
147	Timeout {
148		/// The operation that timed out
149		operation:String,
150
151		/// The timeout duration in milliseconds
152		timeout_ms:u64,
153	},
154
155	/// Invalid argument error
156	InvalidArgument {
157		/// The argument name
158		argument_name:String,
159
160		/// The error reason
161		reason:String,
162	},
163
164	/// Not implemented error
165	NotImplemented {
166		/// The feature that is not implemented
167		feature:String,
168	},
169
170	/// Permission denied error
171	PermissionDenied {
172		/// The resource that was denied
173		resource:String,
174
175		/// The error reason
176		reason:String,
177	},
178
179	/// Resource exhausted error
180	ResourceExhausted {
181		/// The resource that was exhausted
182		resource:String,
183
184		/// The error reason
185		reason:String,
186	},
187
188	/// Internal error
189	InternalError {
190		/// The error reason
191		reason:String,
192
193		/// Optional backtrace (skipped during serialization)
194		#[serde(skip)]
195		backtrace:Option<String>,
196	},
197}
198
199impl GroveError {
200	/// Create extension not found error
201	pub fn extension_not_found(extension_id:impl Into<String>) -> Self {
202		Self::ExtensionNotFound { extension_id:extension_id.into(), message:None }
203	}
204
205	/// Create extension load failed error
206	pub fn extension_load_failed(extension_id:impl Into<String>, reason:impl Into<String>) -> Self {
207		Self::ExtensionLoadFailed { extension_id:extension_id.into(), reason:reason.into(), path:None }
208	}
209
210	/// Create activation failed error
211	pub fn activation_failed(extension_id:impl Into<String>, reason:impl Into<String>) -> Self {
212		Self::ActivationFailed { extension_id:extension_id.into(), reason:reason.into() }
213	}
214
215	/// Create WASM runtime error
216	pub fn wasm_runtime_error(reason:impl Into<String>) -> Self {
217		Self::WASMRuntimeError { reason:reason.into(), module_id:None }
218	}
219
220	/// Create transport error
221	pub fn transport_error(transport_type:impl Into<String>, reason:impl Into<String>) -> Self {
222		Self::TransportError { transport_type:transport_type.into(), reason:reason.into() }
223	}
224
225	/// Create connection error
226	pub fn connection_error(endpoint:impl Into<String>, reason:impl Into<String>) -> Self {
227		Self::ConnectionError { endpoint:endpoint.into(), reason:reason.into() }
228	}
229
230	/// Create API error
231	pub fn api_error(api_method:impl Into<String>, reason:impl Into<String>) -> Self {
232		Self::APIError { api_method:api_method.into(), reason:reason.into(), error_code:None }
233	}
234
235	/// Create timeout error
236	pub fn timeout(operation:impl Into<String>, timeout_ms:u64) -> Self {
237		Self::Timeout { operation:operation.into(), timeout_ms }
238	}
239
240	/// Create invalid argument error
241	pub fn invalid_argument(argument_name:impl Into<String>, reason:impl Into<String>) -> Self {
242		Self::InvalidArgument { argument_name:argument_name.into(), reason:reason.into() }
243	}
244
245	/// Create not implemented error
246	pub fn not_implemented(feature:impl Into<String>) -> Self { Self::NotImplemented { feature:feature.into() } }
247
248	/// Get error code for categorization
249	pub fn error_code(&self) -> &'static str {
250		match self {
251			Self::ExtensionNotFound { .. } => "EXT_NOT_FOUND",
252
253			Self::ExtensionLoadFailed { .. } => "EXT_LOAD_FAILED",
254
255			Self::ActivationFailed { .. } => "ACTIVATION_FAILED",
256
257			Self::DeactivationFailed { .. } => "DEACTIVATION_FAILED",
258
259			Self::WASMRuntimeError { .. } => "WASM_RUNTIME_ERROR",
260
261			Self::WASMCompilationFailed { .. } => "WASM_COMPILATION_FAILED",
262
263			Self::WASMModuleNotFound { .. } => "WASM_MODULE_NOT_FOUND",
264
265			Self::TransportError { .. } => "TRANSPORT_ERROR",
266
267			Self::ConnectionError { .. } => "CONNECTION_ERROR",
268
269			Self::APIError { .. } => "API_ERROR",
270
271			Self::ConfigurationError { .. } => "CONFIGURATION_ERROR",
272
273			Self::IoError { .. } => "IO_ERROR",
274
275			Self::SerializationError { .. } => "SERIALIZATION_ERROR",
276
277			Self::DeserializationError { .. } => "DESERIALIZATION_ERROR",
278
279			Self::Timeout { .. } => "TIMEOUT",
280
281			Self::InvalidArgument { .. } => "INVALID_ARGUMENT",
282
283			Self::NotImplemented { .. } => "NOT_IMPLEMENTED",
284
285			Self::PermissionDenied { .. } => "PERMISSION_DENIED",
286
287			Self::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED",
288
289			Self::InternalError { .. } => "INTERNAL_ERROR",
290		}
291	}
292
293	/// Check if error is recoverable
294	pub fn is_recoverable(&self) -> bool {
295		matches!(
296			self,
297			Self::Timeout { .. }
298				| Self::TransportError { .. }
299				| Self::ConnectionError { .. }
300				| Self::ResourceExhausted { .. }
301		)
302	}
303
304	/// Check if error is transient (can be retried)
305	pub fn is_transient(&self) -> bool {
306		matches!(
307			self,
308			Self::Timeout { .. } | Self::TransportError { .. } | Self::ConnectionError { .. }
309		)
310	}
311}
312
313impl fmt::Display for GroveError {
314	fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
315		match self {
316			Self::ExtensionNotFound { extension_id, message } => {
317				if let Some(msg) = message {
318					write!(f, "Extension not found: {} - {}", extension_id, msg)
319				} else {
320					write!(f, "Extension not found: {}", extension_id)
321				}
322			},
323
324			Self::ExtensionLoadFailed { extension_id, reason, path } => {
325				if let Some(path) = path {
326					write!(f, "Failed to load extension #{:?}: {} - {}", path, extension_id, reason)
327				} else {
328					write!(f, "Failed to load extension {}: {}", extension_id, reason)
329				}
330			},
331
332			Self::ActivationFailed { extension_id, reason } => {
333				write!(f, "Activation failed for extension {}: {}", extension_id, reason)
334			},
335
336			Self::DeactivationFailed { extension_id, reason } => {
337				write!(f, "Deactivation failed for extension {}: {}", extension_id, reason)
338			},
339
340			Self::WASMRuntimeError { reason, module_id } => {
341				if let Some(id) = module_id {
342					write!(f, "WASM runtime error for module {}: {}", id, reason)
343				} else {
344					write!(f, "WASM runtime error: {}", reason)
345				}
346			},
347
348			Self::WASMCompilationFailed { reason, module_path } => {
349				if let Some(path) = module_path {
350					write!(f, "WASM compilation failed for {:?}: {}", path, reason)
351				} else {
352					write!(f, "WASM compilation failed: {}", reason)
353				}
354			},
355
356			Self::WASMModuleNotFound { module_id } => {
357				write!(f, "WASM module not found: {}", module_id)
358			},
359
360			Self::TransportError { transport_type, reason } => {
361				write!(f, "Transport error ({:?}): {}", transport_type, reason)
362			},
363
364			Self::ConnectionError { endpoint, reason } => {
365				write!(f, "Connection error to {}: {}", endpoint, reason)
366			},
367
368			Self::APIError { api_method, reason, .. } => {
369				write!(f, "API error for {}: {}", api_method, reason)
370			},
371
372			Self::ConfigurationError { key, reason } => {
373				write!(f, "Configuration error for '{}': {}", key, reason)
374			},
375
376			Self::IoError { operation, reason, .. } => {
377				write!(f, "I/O error for operation '{}': {}", operation, reason)
378			},
379
380			Self::SerializationError { type_name, reason } => {
381				write!(f, "Serialization error for type '{}': {}", type_name, reason)
382			},
383
384			Self::DeserializationError { type_name, reason } => {
385				write!(f, "Deserialization error for type '{}': {}", type_name, reason)
386			},
387
388			Self::Timeout { operation, timeout_ms } => {
389				write!(f, "Timeout after {}ms for operation: {}", timeout_ms, operation)
390			},
391
392			Self::InvalidArgument { argument_name, reason } => {
393				write!(f, "Invalid argument '{}': {}", argument_name, reason)
394			},
395
396			Self::NotImplemented { feature } => {
397				write!(f, "Feature not implemented: {}", feature)
398			},
399
400			Self::PermissionDenied { resource, reason } => {
401				write!(f, "Permission denied for '{}': {}", resource, reason)
402			},
403
404			Self::ResourceExhausted { resource, reason } => {
405				write!(f, "Resource exhausted '{}': {}", resource, reason)
406			},
407
408			Self::InternalError { reason, .. } => {
409				write!(f, "Internal error: {}", reason)
410			},
411		}
412	}
413}
414
415impl std::error::Error for GroveError {}
416
417/// Convert from std::io::Error
418impl From<std::io::Error> for GroveError {
419	fn from(err:std::io::Error) -> Self {
420		Self::IoError { path:None, operation:"unknown".to_string(), reason:err.to_string() }
421	}
422}
423
424/// Convert from serde_json::Error
425impl From<serde_json::Error> for GroveError {
426	fn from(err:serde_json::Error) -> Self {
427		if err.is_io() {
428			Self::IoError { path:None, operation:"serde_json".to_string(), reason:err.to_string() }
429		} else {
430			Self::DeserializationError { type_name:"unknown".to_string(), reason:err.to_string() }
431		}
432	}
433}
434
435/// Result extension trait for error handling
436pub trait ResultExt<T> {
437	/// Map error to GroveError
438	fn map_grove_error(self, context:impl Into<String>) -> GroveResult<T>;
439}
440
441impl<T, E> ResultExt<T> for Result<T, E>
442where
443	E: std::error::Error + Send + Sync + 'static,
444{
445	fn map_grove_error(self, context:impl Into<String>) -> GroveResult<T> {
446		self.map_err(|e| {
447			GroveError::InternalError {
448				reason:format!("{}: {}", context.into(), e),
449				backtrace:std::backtrace::Backtrace::capture().to_string().into(),
450			}
451		})
452	}
453}
454
455#[cfg(test)]
456mod tests {
457
458	use super::*;
459
460	#[test]
461	fn test_error_creation() {
462		let err = GroveError::extension_not_found("test.ext");
463
464		assert_eq!(err.error_code(), "EXT_NOT_FOUND");
465	}
466
467	#[test]
468	fn test_error_display() {
469		let err = GroveError::activation_failed("test.ext", "timeout");
470
471		assert!(err.to_string().contains("test.ext"));
472
473		assert!(err.to_string().contains("timeout"));
474	}
475
476	#[test]
477	fn test_error_retryable() {
478		let timeout = GroveError::timeout("test", 5000);
479
480		assert!(timeout.is_transient());
481
482		assert!(timeout.is_recoverable());
483
484		let not_found = GroveError::extension_not_found("test.ext");
485
486		assert!(!not_found.is_transient());
487
488		assert!(!not_found.is_recoverable());
489	}
490
491	#[test]
492	fn test_io_error_conversion() {
493		let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
494
495		let grove_err = GroveError::from(io_err);
496
497		assert_eq!(grove_err.error_code(), "IO_ERROR");
498	}
499}