1use 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 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 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 pub fn severity(mut self, severity: ErrorSeverity) -> Self {
203 self.severity = severity;
204 self
205 }
206
207 pub fn source(mut self, source: impl Into<String>) -> Self {
209 self.source = source.into();
210 self
211 }
212
213 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 pub fn correlation_id(mut self, correlation_id: Uuid) -> Self {
221 self.correlation_id = Some(correlation_id);
222 self
223 }
224
225 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 pub fn with_metadata(mut self, metadata: crate::types::Metadata) -> Self {
233 self.metadata.extend(metadata);
234 self
235 }
236
237 pub fn caused_by(mut self, cause: impl fmt::Display) -> Self {
239 self.causes.push(cause.to_string());
240 self
241 }
242
243 pub fn should_handle(&self) -> bool {
245 matches!(self.severity, ErrorSeverity::Low | ErrorSeverity::Medium)
246 }
247
248 pub fn is_critical(&self) -> bool {
250 matches!(self.severity, ErrorSeverity::Critical)
251 }
252
253 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 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 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 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 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 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 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 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 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 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
418pub trait ResultExt<T> {
420 fn with_context<F>(self, f: F) -> Result<T>
422 where
423 F: FnOnce() -> String;
424
425 fn with_source(self, source: impl Into<String>) -> Result<T>;
427
428 fn with_plugin(self, plugin_id: impl Into<String>) -> Result<T>;
430
431 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}