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		/// Optional error message
19		message:Option<String>,
20	},
21
22	/// Extension loading failed error
23	ExtensionLoadFailed {
24		/// The extension identifier
25		extension_id:String,
26		/// The failure reason
27		reason:String,
28		/// Optional path to the extension
29		path:Option<String>,
30	},
31
32	/// Extension activation failed error
33	ActivationFailed {
34		/// The extension identifier
35		extension_id:String,
36		/// The failure reason
37		reason:String,
38	},
39
40	/// Extension deactivation failed error
41	DeactivationFailed {
42		/// The extension identifier
43		extension_id:String,
44		/// The failure reason
45		reason:String,
46	},
47
48	/// WASM runtime error
49	WASMRuntimeError {
50		/// The error reason
51		reason:String,
52		/// Optional module identifier
53		module_id:Option<String>,
54	},
55
56	/// WASM compilation failed error
57	WASMCompilationFailed {
58		/// The failure reason
59		reason:String,
60		/// Optional path to the module
61		module_path:Option<String>,
62	},
63
64	/// WASM module not found error
65	WASMModuleNotFound {
66		/// The module identifier
67		module_id:String,
68	},
69
70	/// Transport error
71	TransportError {
72		/// The transport type
73		transport_type:String,
74		/// The error reason
75		reason:String,
76	},
77
78	/// Connection error
79	ConnectionError {
80		/// The endpoint that failed
81		endpoint:String,
82		/// The error reason
83		reason:String,
84	},
85
86	/// API call error
87	APIError {
88		/// The API method that failed
89		api_method:String,
90		/// The error reason
91		reason:String,
92		/// Optional error code
93		error_code:Option<i32>,
94	},
95
96	/// Configuration error
97	ConfigurationError {
98		/// The configuration key
99		key:String,
100		/// The error reason
101		reason:String,
102	},
103
104	/// I/O error
105	IoError {
106		/// Optional path related to the error
107		path:Option<String>,
108		/// The operation that failed
109		operation:String,
110		/// The error reason
111		reason:String,
112	},
113
114	/// Serialization error
115	SerializationError {
116		/// The type name being serialized
117		type_name:String,
118		/// The error reason
119		reason:String,
120	},
121
122	/// Deserialization error
123	DeserializationError {
124		/// The type name being deserialized
125		type_name:String,
126		/// The error reason
127		reason:String,
128	},
129
130	/// Timeout error
131	Timeout {
132		/// The operation that timed out
133		operation:String,
134		/// The timeout duration in milliseconds
135		timeout_ms:u64,
136	},
137
138	/// Invalid argument error
139	InvalidArgument {
140		/// The argument name
141		argument_name:String,
142		/// The error reason
143		reason:String,
144	},
145
146	/// Not implemented error
147	NotImplemented {
148		/// The feature that is not implemented
149		feature:String,
150	},
151
152	/// Permission denied error
153	PermissionDenied {
154		/// The resource that was denied
155		resource:String,
156		/// The error reason
157		reason:String,
158	},
159
160	/// Resource exhausted error
161	ResourceExhausted {
162		/// The resource that was exhausted
163		resource:String,
164		/// The error reason
165		reason:String,
166	},
167
168	/// Internal error
169	InternalError {
170		/// The error reason
171		reason:String,
172		/// Optional backtrace (skipped during serialization)
173		#[serde(skip)]
174		backtrace: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}