qorzen_oxide/
error.rs

1// src/error.rs - Enhanced error handling with platform and plugin support
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use uuid::Uuid;
7
8pub type Result<T> = std::result::Result<T, Error>;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
11pub enum ErrorSeverity {
12    Low,
13    Medium,
14    High,
15    Critical,
16}
17
18impl fmt::Display for ErrorSeverity {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::Low => write!(f, "LOW"),
22            Self::Medium => write!(f, "MEDIUM"),
23            Self::High => write!(f, "HIGH"),
24            Self::Critical => write!(f, "CRITICAL"),
25        }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub enum ErrorKind {
31    Configuration {
32        key: Option<String>,
33        validation_errors: Vec<String>,
34    },
35    Manager {
36        manager_name: String,
37        operation: ManagerOperation,
38    },
39    Event {
40        event_type: Option<String>,
41        subscriber_id: Option<Uuid>,
42        operation: EventOperation,
43    },
44    Task {
45        task_id: Option<Uuid>,
46        task_name: Option<String>,
47        cancelled: bool,
48    },
49    File {
50        path: Option<String>,
51        operation: FileOperation,
52    },
53    Concurrency {
54        thread_id: Option<String>,
55        operation: ConcurrencyOperation,
56    },
57    Plugin {
58        plugin_id: Option<String>,
59        plugin_name: Option<String>,
60        dependency_missing: Option<String>,
61    },
62    Platform {
63        platform: String,
64        feature: String,
65        fallback_available: bool,
66    },
67    Permission {
68        required_permission: String,
69        user_role: Option<String>,
70    },
71    Network {
72        status_code: Option<u16>,
73        endpoint: Option<String>,
74    },
75    Database {
76        query: Option<String>,
77        connection_id: Option<String>,
78    },
79    Security {
80        user_id: Option<String>,
81        permission: Option<String>,
82    },
83    Validation {
84        field: Option<String>,
85        rules: Vec<String>,
86    },
87    Authentication {
88        provider: Option<String>,
89        reason: String,
90    },
91    Authorization {
92        resource: String,
93        action: String,
94        user_id: Option<String>,
95    },
96    Application,
97    Io,
98    Serialization,
99    Timeout,
100    ResourceExhausted,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub enum ManagerOperation {
105    Initialize,
106    Shutdown,
107    Configure,
108    Pause,
109    Resume,
110    Register,
111    Unregister,
112    Operation(String),
113    Reload,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub enum ConfigOperation {
118    Get,
119    Set,
120    Reload,
121    Validate,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub enum EventOperation {
126    Publish,
127    Subscribe,
128    Unsubscribe,
129    Process,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub enum FileOperation {
134    Read,
135    Write,
136    Delete,
137    Copy,
138    Move,
139    CreateDirectory,
140    Metadata,
141    Lock,
142    Watch,
143    Compress,
144    Decompress,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
148pub enum ConcurrencyOperation {
149    ThreadPool,
150    Spawn,
151    Sync,
152    Channel,
153    Lock,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct Error {
158    pub id: Uuid,
159    pub kind: ErrorKind,
160    pub message: String,
161    pub severity: ErrorSeverity,
162    pub source: String,
163    pub plugin_id: Option<String>,
164    pub correlation_id: Option<Uuid>,
165    pub timestamp: DateTime<Utc>,
166    pub metadata: crate::types::Metadata,
167    pub backtrace: Option<String>,
168    pub causes: Vec<String>,
169}
170
171impl Error {
172    /// Creates a new error with the specified kind and message
173    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
174        Self {
175            id: Uuid::new_v4(),
176            kind,
177            message: message.into(),
178            severity: ErrorSeverity::Medium,
179            source: "unknown".to_string(),
180            plugin_id: None,
181            correlation_id: None,
182            timestamp: Utc::now(),
183            metadata: std::collections::HashMap::new(),
184            backtrace: Self::capture_backtrace(),
185            causes: Vec::new(),
186        }
187    }
188
189    /// Capture backtrace if available on the platform
190    fn capture_backtrace() -> Option<String> {
191        #[cfg(not(target_arch = "wasm32"))]
192        {
193            Some(std::backtrace::Backtrace::capture().to_string())
194        }
195        #[cfg(target_arch = "wasm32")]
196        {
197            None
198        }
199    }
200
201    /// Sets the error severity
202    pub fn severity(mut self, severity: ErrorSeverity) -> Self {
203        self.severity = severity;
204        self
205    }
206
207    /// Sets the error source
208    pub fn source(mut self, source: impl Into<String>) -> Self {
209        self.source = source.into();
210        self
211    }
212
213    /// Sets the plugin ID
214    pub fn plugin_id(mut self, plugin_id: impl Into<String>) -> Self {
215        self.plugin_id = Some(plugin_id.into());
216        self
217    }
218
219    /// Sets the correlation ID
220    pub fn correlation_id(mut self, correlation_id: Uuid) -> Self {
221        self.correlation_id = Some(correlation_id);
222        self
223    }
224
225    /// Adds metadata to the error
226    pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
227        self.metadata.insert(key.into(), value);
228        self
229    }
230
231    /// Adds metadata collection to the error
232    pub fn with_metadata(mut self, metadata: crate::types::Metadata) -> Self {
233        self.metadata.extend(metadata);
234        self
235    }
236
237    /// Adds a cause to the error chain
238    pub fn caused_by(mut self, cause: impl fmt::Display) -> Self {
239        self.causes.push(cause.to_string());
240        self
241    }
242
243    /// Checks if the error should be handled automatically
244    pub fn should_handle(&self) -> bool {
245        matches!(self.severity, ErrorSeverity::Low | ErrorSeverity::Medium)
246    }
247
248    /// Checks if the error is critical
249    pub fn is_critical(&self) -> bool {
250        matches!(self.severity, ErrorSeverity::Critical)
251    }
252
253    /// Creates a configuration error
254    pub fn config(message: impl Into<String>) -> Self {
255        Self::new(
256            ErrorKind::Configuration {
257                key: None,
258                validation_errors: Vec::new(),
259            },
260            message,
261        )
262        .severity(ErrorSeverity::High)
263    }
264
265    /// Creates a manager operation error
266    pub fn manager(
267        manager_name: impl Into<String>,
268        operation: ManagerOperation,
269        message: impl Into<String>,
270    ) -> Self {
271        Self::new(
272            ErrorKind::Manager {
273                manager_name: manager_name.into(),
274                operation,
275            },
276            message,
277        )
278        .severity(ErrorSeverity::High)
279    }
280
281    /// Creates a platform-specific error
282    pub fn platform(
283        platform: impl Into<String>,
284        feature: impl Into<String>,
285        message: impl Into<String>,
286    ) -> Self {
287        Self::new(
288            ErrorKind::Platform {
289                platform: platform.into(),
290                feature: feature.into(),
291                fallback_available: false,
292            },
293            message,
294        )
295        .severity(ErrorSeverity::Medium)
296    }
297
298    /// Creates a permission error
299    pub fn permission(required_permission: impl Into<String>, message: impl Into<String>) -> Self {
300        Self::new(
301            ErrorKind::Permission {
302                required_permission: required_permission.into(),
303                user_role: None,
304            },
305            message,
306        )
307        .severity(ErrorSeverity::High)
308    }
309
310    /// Creates a plugin error
311    pub fn plugin(plugin_id: impl Into<String>, message: impl Into<String>) -> Self {
312        Self::new(
313            ErrorKind::Plugin {
314                plugin_id: Some(plugin_id.into()),
315                plugin_name: None,
316                dependency_missing: None,
317            },
318            message,
319        )
320        .severity(ErrorSeverity::Medium)
321    }
322
323    /// Creates an authentication error
324    pub fn authentication(message: impl Into<String>) -> Self {
325        let msg = message.into();
326        Self::new(
327            ErrorKind::Authentication {
328                provider: None,
329                reason: msg.clone(),
330            },
331            msg,
332        )
333        .severity(ErrorSeverity::High)
334    }
335
336    /// Creates an authorization error
337    pub fn authorization(
338        resource: impl Into<String>,
339        action: impl Into<String>,
340        message: impl Into<String>,
341    ) -> Self {
342        Self::new(
343            ErrorKind::Authorization {
344                resource: resource.into(),
345                action: action.into(),
346                user_id: None,
347            },
348            message,
349        )
350        .severity(ErrorSeverity::High)
351    }
352
353    /// Creates a file operation error
354    pub fn file(
355        path: impl Into<String>,
356        operation: FileOperation,
357        message: impl Into<String>,
358    ) -> Self {
359        Self::new(
360            ErrorKind::File {
361                path: Some(path.into()),
362                operation,
363            },
364            message,
365        )
366    }
367
368    /// Creates a task error
369    pub fn task(
370        task_id: Option<uuid::Uuid>,
371        task_name: Option<String>,
372        message: impl Into<String>,
373    ) -> Self {
374        Self::new(
375            ErrorKind::Task {
376                task_id,
377                task_name,
378                cancelled: false,
379            },
380            message,
381        )
382    }
383
384    /// Creates a timeout error
385    pub fn timeout(message: impl Into<String>) -> Self {
386        Self::new(ErrorKind::Timeout, message)
387    }
388}
389
390impl fmt::Display for Error {
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        write!(
393            f,
394            "[{}] {} ({}): {}",
395            self.severity, self.source, self.id, self.message
396        )
397    }
398}
399
400impl std::error::Error for Error {
401    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
402        None
403    }
404}
405
406impl From<std::io::Error> for Error {
407    fn from(err: std::io::Error) -> Self {
408        let msg = err.to_string();
409
410        let mut error = Error::new(ErrorKind::Io, msg);
411        error.source = "std::io::Error".to_string();
412        error.severity = ErrorSeverity::High;
413
414        error
415    }
416}
417
418/// Extension trait for Results to add context
419pub trait ResultExt<T> {
420    /// Adds context to an error
421    fn with_context<F>(self, f: F) -> Result<T>
422    where
423        F: FnOnce() -> String;
424
425    /// Sets the error source
426    fn with_source(self, source: impl Into<String>) -> Result<T>;
427
428    /// Sets the plugin ID
429    fn with_plugin(self, plugin_id: impl Into<String>) -> Result<T>;
430
431    /// Sets the correlation ID
432    fn with_correlation(self, correlation_id: Uuid) -> Result<T>;
433}
434
435impl<T, E> ResultExt<T> for std::result::Result<T, E>
436where
437    E: std::error::Error + Send + Sync + 'static,
438{
439    fn with_context<F>(self, f: F) -> Result<T>
440    where
441        F: FnOnce() -> String,
442    {
443        self.map_err(|e| Error::new(ErrorKind::Application, f()).caused_by(e))
444    }
445
446    fn with_source(self, source: impl Into<String>) -> Result<T> {
447        self.map_err(|e| {
448            Error::new(ErrorKind::Application, e.to_string())
449                .source(source)
450                .caused_by(e)
451        })
452    }
453
454    fn with_plugin(self, plugin_id: impl Into<String>) -> Result<T> {
455        self.map_err(|e| {
456            Error::new(
457                ErrorKind::Plugin {
458                    plugin_id: Some(plugin_id.into()),
459                    plugin_name: None,
460                    dependency_missing: None,
461                },
462                e.to_string(),
463            )
464            .caused_by(e)
465        })
466    }
467
468    fn with_correlation(self, correlation_id: Uuid) -> Result<T> {
469        self.map_err(|e| {
470            Error::new(ErrorKind::Application, e.to_string())
471                .correlation_id(correlation_id)
472                .caused_by(e)
473        })
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    #[test]
482    fn test_error_creation() {
483        let error = Error::config("Invalid configuration value")
484            .source("config_manager")
485            .metadata(
486                "key",
487                serde_json::Value::String("database.host".to_string()),
488            );
489
490        assert_eq!(error.severity, ErrorSeverity::High);
491        assert_eq!(error.source, "config_manager");
492        assert!(matches!(error.kind, ErrorKind::Configuration { .. }));
493        assert!(error.metadata.contains_key("key"));
494    }
495
496    #[test]
497    fn test_platform_error() {
498        let error = Error::platform("wasm", "filesystem", "File API not available");
499        assert!(matches!(error.kind, ErrorKind::Platform { .. }));
500        assert_eq!(error.severity, ErrorSeverity::Medium);
501    }
502
503    #[test]
504    fn test_permission_error() {
505        let error = Error::permission("admin.users.read", "Access denied");
506        assert!(matches!(error.kind, ErrorKind::Permission { .. }));
507        assert_eq!(error.severity, ErrorSeverity::High);
508    }
509}