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
15ExtensionNotFound {
16/// The extension identifier
17extension_id:String,
18/// Optional error message
19message:Option<String>,
20},
21
22/// Extension loading failed error
23ExtensionLoadFailed {
24/// The extension identifier
25extension_id:String,
26/// The failure reason
27reason:String,
28/// Optional path to the extension
29path:Option<String>,
30},
31
32/// Extension activation failed error
33ActivationFailed {
34/// The extension identifier
35extension_id:String,
36/// The failure reason
37reason:String,
38},
39
40/// Extension deactivation failed error
41DeactivationFailed {
42/// The extension identifier
43extension_id:String,
44/// The failure reason
45reason:String,
46},
47
48/// WASM runtime error
49WASMRuntimeError {
50/// The error reason
51reason:String,
52/// Optional module identifier
53module_id:Option<String>,
54},
55
56/// WASM compilation failed error
57WASMCompilationFailed {
58/// The failure reason
59reason:String,
60/// Optional path to the module
61module_path:Option<String>,
62},
63
64/// WASM module not found error
65WASMModuleNotFound {
66/// The module identifier
67module_id:String,
68},
69
70/// Transport error
71TransportError {
72/// The transport type
73transport_type:String,
74/// The error reason
75reason:String,
76},
77
78/// Connection error
79ConnectionError {
80/// The endpoint that failed
81endpoint:String,
82/// The error reason
83reason:String,
84},
85
86/// API call error
87APIError {
88/// The API method that failed
89api_method:String,
90/// The error reason
91reason:String,
92/// Optional error code
93error_code:Option<i32>,
94},
95
96/// Configuration error
97ConfigurationError {
98/// The configuration key
99key:String,
100/// The error reason
101reason:String,
102},
103
104/// I/O error
105IoError {
106/// Optional path related to the error
107path:Option<String>,
108/// The operation that failed
109operation:String,
110/// The error reason
111reason:String,
112},
113
114/// Serialization error
115SerializationError {
116/// The type name being serialized
117type_name:String,
118/// The error reason
119reason:String,
120},
121
122/// Deserialization error
123DeserializationError {
124/// The type name being deserialized
125type_name:String,
126/// The error reason
127reason:String,
128},
129
130/// Timeout error
131Timeout {
132/// The operation that timed out
133operation:String,
134/// The timeout duration in milliseconds
135timeout_ms:u64,
136},
137
138/// Invalid argument error
139InvalidArgument {
140/// The argument name
141argument_name:String,
142/// The error reason
143reason:String,
144},
145
146/// Not implemented error
147NotImplemented {
148/// The feature that is not implemented
149feature:String,
150},
151
152/// Permission denied error
153PermissionDenied {
154/// The resource that was denied
155resource:String,
156/// The error reason
157reason:String,
158},
159
160/// Resource exhausted error
161ResourceExhausted {
162/// The resource that was exhausted
163resource:String,
164/// The error reason
165reason:String,
166},
167
168/// Internal error
169InternalError {
170/// The error reason
171reason:String,
172/// Optional backtrace (skipped during serialization)
173#[serde(skip)]
174backtrace:Option<String>,
175},
176}
177
178impl GroveError {
179	/// Create extension not found error
180	pub fn extension_not_found(extension_id:impl Into<String>) -> Self {
181		Self::ExtensionNotFound { extension_id:extension_id.into(), message:None }
182	}
183
184	/// Create extension load failed error
185	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	/// Create activation failed error
190	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	/// Create WASM runtime error
195	pub fn wasm_runtime_error(reason:impl Into<String>) -> Self {
196		Self::WASMRuntimeError { reason:reason.into(), module_id:None }
197	}
198
199	/// Create transport error
200	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	/// Create connection error
205	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	/// Create API error
210	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	/// Create timeout error
215	pub fn timeout(operation:impl Into<String>, timeout_ms:u64) -> Self {
216		Self::Timeout { operation:operation.into(), timeout_ms }
217	}
218
219	/// Create invalid argument error
220	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	/// Create not implemented error
225	pub fn not_implemented(feature:impl Into<String>) -> Self { Self::NotImplemented { feature:feature.into() } }
226
227	/// Get error code for categorization
228	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	/// Check if error is recoverable
254	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	/// Check if error is transient (can be retried)
265	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
358/// Convert from std::io::Error
359impl 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
365/// Convert from serde_json::Error
366impl 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
376/// Result extension trait for error handling
377pub trait ResultExt<T> {
378	/// Map error to GroveError
379	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}