proc_daemon/
error.rs

1//! Error handling for the proc-daemon framework.
2//!
3//! This module provides comprehensive error types for all daemon operations,
4//! designed for both programmatic handling and human-readable error messages.
5//!
6//! # Features
7//!
8//! * Structured error codes for all error types
9//! * Source error capture for better context
10//! * Backtrace support for production debugging
11//! * Serializable errors for structured logging (requires `serde` feature)
12//! * Categorization for metrics and monitoring
13//!
14//! # Error Structure
15//!
16//! Each error variant contains:
17//!
18//! * **Error Code**: A unique identifier for programmatic handling and metrics
19//! * **Message**: A human-readable description of the error
20//! * **Source**: Optional underlying error that caused this error
21//! * **Context-specific fields**: Additional fields specific to each error type
22//!
23//! # Usage Examples
24//!
25//! ## Basic Error Handling
26//!
27//! ```no_run
28//! use proc_daemon::{Error, Result};
29//!
30//! fn example_function() -> Result<()> {
31//!     // Use prebuilt constructor
32//!     let something_failed = true;
33//!     if something_failed {
34//!         return Err(Error::config("Invalid configuration"));
35//!     }
36//!
37//!     // Or with specific error code
38//!     let config_missing = false;
39//!     if config_missing {
40//!         // Code using specific error codes would go here
41//!         return Err(Error::config("Configuration file not found"));
42//!     }
43//!     
44//!     Ok(())
45//! }
46//! ```
47//!
48//! ## Capturing Source Errors
49//!
50//! ```no_run
51//! use proc_daemon::{Error, Result};
52//! use std::fs::File;
53//!
54//! fn read_config(path: &str) -> Result<String> {
55//!     let file = match File::open(path) {
56//!         Ok(f) => f,
57//!         Err(err) => return Err(Error::io_with_source(
58//!             format!("Failed to open config file: {}", path),
59//!             err
60//!         )),
61//!     };
62//!     
63//!     // Process file...
64//!     Ok("Config content".to_string())
65//! }
66//! ```
67//!
68//! ## Error Handling Best Practices
69//!
70//! 1. Always include meaningful error messages
71//! 2. Capture source errors when available
72//! 3. Use specific error codes for monitoring and metrics
73//! 4. Enable backtraces in development and test environments
74//!
75//! # Feature Flags
76//!
77//! ## Backtrace Support
78//!
79//! Enable backtrace support with the `backtrace` feature:
80//!
81//! ```toml
82//! [dependencies]
83//! proc-daemon = { version = "0.1.0", features = ["backtrace"] }
84//! ```
85//!
86//! ## Serialization
87//!
88//! Enable error serialization with the `serde` feature:
89//!
90//! ```toml
91//! [dependencies]
92//! proc-daemon = { version = "0.1.0", features = ["serde"] }
93//! ```
94//!
95//! This allows errors to be serialized for structured logging or metrics collection.
96
97/// Result type alias for proc-daemon operations.
98pub type Result<T> = std::result::Result<T, Error>;
99
100// BacktraceError implementation for capturing backtraces
101#[cfg(feature = "backtrace")]
102pub use std::backtrace::Backtrace;
103
104/// Error type that captures backtraces
105#[cfg(feature = "backtrace")]
106#[derive(Debug)]
107pub struct BacktraceError {
108    /// The message describing this error
109    message: String,
110    /// The backtrace captured when this error was created
111    backtrace: Backtrace,
112    /// Optional source error
113    source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
114}
115
116#[cfg(feature = "backtrace")]
117impl BacktraceError {
118    /// Create a new backtrace error with a message
119    #[allow(dead_code)]
120    pub fn new<S: Into<String>>(message: S) -> Self {
121        Self {
122            message: message.into(),
123            backtrace: Backtrace::capture(),
124            source: None,
125        }
126    }
127
128    /// Create a new backtrace error with a message and source
129    #[allow(dead_code)]
130    pub fn with_source<S: Into<String>, E: std::error::Error + Send + Sync + 'static>(
131        message: S,
132        source: E,
133    ) -> Self {
134        Self {
135            message: message.into(),
136            backtrace: Backtrace::capture(),
137            source: Some(Box::new(source)),
138        }
139    }
140
141    /// Get the backtrace
142    pub const fn backtrace(&self) -> &Backtrace {
143        &self.backtrace
144    }
145}
146
147#[cfg(feature = "backtrace")]
148impl std::fmt::Display for BacktraceError {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(f, "{}", self.message)
151    }
152}
153
154#[cfg(feature = "backtrace")]
155impl std::error::Error for BacktraceError {
156    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
157        self.source
158            .as_ref()
159            .map(|s| s.as_ref() as &(dyn std::error::Error + 'static))
160    }
161}
162
163/// Error code enum for categorizing and identifying errors
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
165#[non_exhaustive]
166pub enum ErrorCode {
167    // Configuration errors: 1000-1999
168    ConfigInvalid = 1000,
169    ConfigParse = 1001,
170    ConfigMissing = 1002,
171    ConfigTypeMismatch = 1003,
172
173    // Signal handling errors: 2000-2999
174    SignalRegisterFailed = 2000,
175    SignalSendFailed = 2001,
176    SignalInvalid = 2002,
177
178    // Shutdown errors: 3000-3999
179    ShutdownTimeout = 3000,
180    ShutdownAlreadyInProgress = 3001,
181    ShutdownFailed = 3002,
182
183    // Subsystem errors: 4000-4999
184    SubsystemStartFailed = 4000,
185    SubsystemStopFailed = 4001,
186    SubsystemNotFound = 4002,
187    SubsystemAlreadyRegistered = 4003,
188    SubsystemStateInvalid = 4004,
189
190    // IO errors: 5000-5999
191    IoError = 5000,
192    FileNotFound = 5001,
193    FilePermissionDenied = 5002,
194
195    // Runtime errors: 6000-6999
196    RuntimePanic = 6000,
197    RuntimeAsyncError = 6001,
198    LockFailed = 6002,
199    RuntimeSpawnError = 6003,
200    MissingRuntime = 6004,
201
202    // Resource errors: 7000-7999
203    ResourceExhaustedMemory = 7000,
204    ResourceExhaustedCpu = 7001,
205    ResourceExhaustedFileDescriptors = 7002,
206
207    // Timeout errors: 8000-8999
208    TimeoutOperation = 8000,
209    TimeoutConnection = 8001,
210
211    // State errors: 9000-9999
212    InvalidStateTransition = 9000,
213    InvalidStateValue = 9001,
214
215    // Platform errors: 10000-10999
216    PlatformNotSupported = 10000,
217    PlatformFeatureNotAvailable = 10001,
218
219    // Unknown/other errors: 99000+
220    Unknown = 99999,
221}
222
223impl std::fmt::Display for ErrorCode {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        write!(f, "{}({})", self.as_str(), *self as i32)
226    }
227}
228
229impl ErrorCode {
230    /// Convert error code to string representation
231    pub const fn as_str(self) -> &'static str {
232        match self {
233            // Configuration errors
234            Self::ConfigInvalid => "CONFIG_INVALID",
235            Self::ConfigParse => "CONFIG_PARSE",
236            Self::ConfigMissing => "CONFIG_MISSING",
237            Self::ConfigTypeMismatch => "CONFIG_TYPE_MISMATCH",
238
239            // Signal errors
240            Self::SignalRegisterFailed => "SIGNAL_REGISTER_FAILED",
241            Self::SignalSendFailed => "SIGNAL_SEND_FAILED",
242            Self::SignalInvalid => "SIGNAL_INVALID",
243
244            // Shutdown errors
245            Self::ShutdownTimeout => "SHUTDOWN_TIMEOUT",
246            Self::ShutdownAlreadyInProgress => "SHUTDOWN_ALREADY_IN_PROGRESS",
247            Self::ShutdownFailed => "SHUTDOWN_FAILED",
248
249            // Subsystem errors
250            Self::SubsystemStartFailed => "SUBSYSTEM_START_FAILED",
251            Self::SubsystemStopFailed => "SUBSYSTEM_STOP_FAILED",
252            Self::SubsystemNotFound => "SUBSYSTEM_NOT_FOUND",
253            Self::SubsystemAlreadyRegistered => "SUBSYSTEM_ALREADY_REGISTERED",
254            Self::SubsystemStateInvalid => "SUBSYSTEM_STATE_INVALID",
255
256            // IO errors
257            Self::IoError => "IO_ERROR",
258            Self::FileNotFound => "FILE_NOT_FOUND",
259            Self::FilePermissionDenied => "FILE_PERMISSION_DENIED",
260
261            // Runtime errors
262            Self::RuntimePanic => "RUNTIME_PANIC",
263            Self::RuntimeAsyncError => "RUNTIME_ASYNC_ERROR",
264            Self::LockFailed => "LOCK_FAILED",
265            Self::RuntimeSpawnError => "RUNTIME_SPAWN_ERROR",
266            Self::MissingRuntime => "MISSING_RUNTIME",
267
268            // Resource errors
269            Self::ResourceExhaustedMemory => "RESOURCE_EXHAUSTED_MEMORY",
270            Self::ResourceExhaustedCpu => "RESOURCE_EXHAUSTED_CPU",
271            Self::ResourceExhaustedFileDescriptors => "RESOURCE_EXHAUSTED_FILE_DESCRIPTORS",
272
273            // Timeout errors
274            Self::TimeoutOperation => "TIMEOUT_OPERATION",
275            Self::TimeoutConnection => "TIMEOUT_CONNECTION",
276
277            // State errors
278            Self::InvalidStateTransition => "INVALID_STATE_TRANSITION",
279            Self::InvalidStateValue => "INVALID_STATE_VALUE",
280
281            // Platform errors
282            Self::PlatformNotSupported => "PLATFORM_NOT_SUPPORTED",
283            Self::PlatformFeatureNotAvailable => "PLATFORM_FEATURE_NOT_AVAILABLE",
284
285            // Unknown errors
286            Self::Unknown => "UNKNOWN_ERROR",
287        }
288    }
289}
290
291/// Comprehensive error type for all daemon operations.
292#[cfg_attr(feature = "serde", derive(serde::Serialize))]
293#[derive(thiserror::Error, Debug)]
294pub enum Error {
295    /// Configuration-related errors
296    #[error("Configuration error [{code}]: {message}")]
297    Config {
298        /// Error code for structured error handling
299        code: ErrorCode,
300        /// Human-readable error message
301        message: String,
302        /// Optional source error for better context
303        #[source]
304        #[cfg_attr(feature = "serde", serde(skip))]
305        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
306    },
307
308    /// Signal handling errors
309    #[error("Signal handling error [{code}]: {message}{signal:?}")]
310    Signal {
311        /// Error code for structured error handling
312        code: ErrorCode,
313        /// Human-readable error message
314        message: String,
315        /// Signal number if applicable
316        signal: Option<i32>,
317        /// Optional source error for better context
318        #[source]
319        #[cfg_attr(feature = "serde", serde(skip))]
320        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
321    },
322
323    /// Shutdown coordination errors
324    #[error("Shutdown error [{code}]: {message}{timeout_ms:?}")]
325    Shutdown {
326        /// Error code for structured error handling
327        code: ErrorCode,
328        /// Human-readable error message
329        message: String,
330        /// Shutdown timeout if applicable
331        timeout_ms: Option<u64>,
332        /// Optional source error for better context
333        #[source]
334        #[cfg_attr(feature = "serde", serde(skip))]
335        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
336    },
337
338    /// Subsystem management errors
339    #[error("Subsystem '{name}' error [{code}]: {message}")]
340    Subsystem {
341        /// Error code for structured error handling
342        code: ErrorCode,
343        /// Name of the subsystem
344        name: String,
345        /// Human-readable error message
346        message: String,
347        /// Optional source error for better context
348        #[source]
349        #[cfg_attr(feature = "serde", serde(skip))]
350        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
351    },
352
353    /// I/O operation errors
354    #[error("I/O error [{code}]: {message}")]
355    Io {
356        /// Error code for structured error handling
357        code: ErrorCode,
358        /// Human-readable error message
359        message: String,
360        /// Optional source error for better context
361        #[source]
362        #[cfg_attr(feature = "serde", serde(skip))]
363        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
364    },
365
366    /// Resource exhaustion errors
367    #[error("Resource exhausted [{code}]: {resource} - {message}")]
368    ResourceExhausted {
369        /// Error code for structured error handling
370        code: ErrorCode,
371        /// Type of resource exhausted
372        resource: String,
373        /// Human-readable error message
374        message: String,
375        /// Optional source error for better context
376        #[source]
377        #[cfg_attr(feature = "serde", serde(skip))]
378        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
379    },
380
381    /// Timeout errors
382    #[error("Operation timed out [{code}] after {timeout_ms}ms: {operation}")]
383    Timeout {
384        /// Error code for structured error handling
385        code: ErrorCode,
386        /// Operation that timed out
387        operation: String,
388        /// Timeout duration in milliseconds
389        timeout_ms: u64,
390        /// Optional source error for better context
391        #[source]
392        #[cfg_attr(feature = "serde", serde(skip))]
393        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
394    },
395
396    /// Invalid state errors
397    #[error("Invalid state [{code}]: {message}{current_state:?}")]
398    InvalidState {
399        /// Error code for structured error handling
400        code: ErrorCode,
401        /// Human-readable error message
402        message: String,
403        /// Current state if applicable
404        current_state: Option<String>,
405        /// Optional source error for better context
406        #[source]
407        #[cfg_attr(feature = "serde", serde(skip))]
408        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
409    },
410
411    /// Platform-specific errors
412    #[error("Platform error [{code}]: {message} (platform: {platform})")]
413    Platform {
414        /// Error code for structured error handling
415        code: ErrorCode,
416        /// Human-readable error message
417        message: String,
418        /// Platform identifier
419        platform: String,
420        /// Optional source error for better context
421        #[source]
422        #[cfg_attr(feature = "serde", serde(skip))]
423        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
424    },
425
426    /// Runtime errors
427    #[error("Runtime error [{code}]: {message}")]
428    Runtime {
429        /// Error code for structured error handling
430        code: ErrorCode,
431        /// Human-readable error message
432        message: String,
433        /// Optional source error for better context
434        #[source]
435        #[cfg_attr(feature = "serde", serde(skip))]
436        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
437    },
438}
439
440impl Error {
441    // Helper methods removed as they're no longer needed with direct field formatting
442
443    #[cfg(feature = "backtrace")]
444    /// Get a backtrace for the error if available
445    #[must_use]
446    pub fn backtrace(&self) -> Option<&Backtrace> {
447        match self {
448            Self::Config {
449                source: Some(err), ..
450            }
451            | Self::Subsystem {
452                source: Some(err), ..
453            }
454            | Self::Io {
455                source: Some(err), ..
456            }
457            | Self::Runtime {
458                source: Some(err), ..
459            }
460            | Self::ResourceExhausted {
461                source: Some(err), ..
462            }
463            | Self::Timeout {
464                source: Some(err), ..
465            }
466            | Self::InvalidState {
467                source: Some(err), ..
468            }
469            | Self::Platform {
470                source: Some(err), ..
471            }
472            | Self::Signal {
473                source: Some(err), ..
474            }
475            | Self::Shutdown {
476                source: Some(err), ..
477            } => err
478                .downcast_ref::<BacktraceError>()
479                .map(BacktraceError::backtrace),
480            _ => None,
481        }
482    }
483
484    /// Create a new configuration error.
485    pub fn config<S: Into<String>>(message: S) -> Self {
486        Self::Config {
487            code: ErrorCode::ConfigInvalid,
488            message: message.into(),
489            source: None,
490        }
491    }
492
493    /// Create a new signal error.
494    pub fn signal<S: Into<String>>(message: S) -> Self {
495        Self::Signal {
496            code: ErrorCode::SignalInvalid,
497            message: message.into(),
498            signal: None,
499            source: None,
500        }
501    }
502
503    /// Create a new signal error with signal number.
504    pub fn signal_with_number<S: Into<String>>(message: S, signal: i32) -> Self {
505        Self::Signal {
506            code: ErrorCode::SignalInvalid,
507            message: message.into(),
508            signal: Some(signal),
509            source: None,
510        }
511    }
512
513    /// Create a new signal error with specific code.
514    pub fn signal_with_code<S: Into<String>>(code: ErrorCode, message: S) -> Self {
515        Self::Signal {
516            code,
517            message: message.into(),
518            signal: None,
519            source: None,
520        }
521    }
522
523    /// Create a new shutdown error.
524    pub fn shutdown<S: Into<String>>(message: S) -> Self {
525        Self::Shutdown {
526            code: ErrorCode::ShutdownFailed,
527            message: message.into(),
528            timeout_ms: None,
529            source: None,
530        }
531    }
532
533    /// Create a new shutdown error with timeout.
534    pub fn shutdown_timeout<S: Into<String>>(message: S, timeout_ms: u64) -> Self {
535        Self::Shutdown {
536            code: ErrorCode::ShutdownTimeout,
537            message: message.into(),
538            timeout_ms: Some(timeout_ms),
539            source: None,
540        }
541    }
542
543    /// Create a new shutdown error with specific error code.
544    pub fn shutdown_with_code<S: Into<String>>(code: ErrorCode, message: S) -> Self {
545        Self::Shutdown {
546            code,
547            message: message.into(),
548            timeout_ms: None,
549            source: None,
550        }
551    }
552
553    /// Create a new subsystem error.
554    pub fn subsystem<S: Into<String>, M: Into<String>>(name: S, message: M) -> Self {
555        Self::Subsystem {
556            code: ErrorCode::SubsystemNotFound,
557            name: name.into(),
558            message: message.into(),
559            source: None,
560        }
561    }
562
563    /// Create a new subsystem error with specific error code.
564    pub fn subsystem_with_code<S: Into<String>, M: Into<String>>(
565        code: ErrorCode,
566        name: S,
567        message: M,
568    ) -> Self {
569        Self::Subsystem {
570            code,
571            name: name.into(),
572            message: message.into(),
573            source: None,
574        }
575    }
576
577    /// Create a new I/O error.
578    pub fn io<S: Into<String>>(message: S) -> Self {
579        Self::Io {
580            code: ErrorCode::IoError,
581            message: message.into(),
582            source: None,
583        }
584    }
585
586    /// Create a new I/O error with source error.
587    pub fn io_with_source<S: Into<String>, E: std::error::Error + Send + Sync + 'static>(
588        message: S,
589        source: E,
590    ) -> Self {
591        Self::Io {
592            code: ErrorCode::IoError,
593            message: message.into(),
594            source: Some(Box::new(source)),
595        }
596    }
597
598    /// Create a new runtime error.
599    pub fn runtime<S: Into<String>>(message: S) -> Self {
600        Self::Runtime {
601            code: ErrorCode::RuntimePanic,
602            message: message.into(),
603            source: None,
604        }
605    }
606
607    /// Create a new runtime error with specific code.
608    pub fn runtime_with_code<S: Into<String>>(code: ErrorCode, message: S) -> Self {
609        Self::Runtime {
610            code,
611            message: message.into(),
612            source: None,
613        }
614    }
615
616    /// Create a new runtime error with source error.
617    pub fn runtime_with_source<S: Into<String>, E: std::error::Error + Send + Sync + 'static>(
618        message: S,
619        source: E,
620    ) -> Self {
621        Self::Runtime {
622            code: ErrorCode::RuntimePanic,
623            message: message.into(),
624            source: Some(Box::new(source)),
625        }
626    }
627
628    /// Create a new resource exhausted error.
629    pub fn resource_exhausted<S: Into<String>, M: Into<String>>(resource: S, message: M) -> Self {
630        Self::ResourceExhausted {
631            code: ErrorCode::ResourceExhaustedMemory,
632            resource: resource.into(),
633            message: message.into(),
634            source: None,
635        }
636    }
637
638    /// Create a new resource exhausted error with specific code.
639    pub fn resource_exhausted_with_code<S: Into<String>, M: Into<String>>(
640        code: ErrorCode,
641        resource: S,
642        message: M,
643    ) -> Self {
644        Self::ResourceExhausted {
645            code,
646            resource: resource.into(),
647            message: message.into(),
648            source: None,
649        }
650    }
651
652    /// Create a new timeout error.
653    pub fn timeout<S: Into<String>>(operation: S, timeout_ms: u64) -> Self {
654        Self::Timeout {
655            code: ErrorCode::TimeoutOperation,
656            operation: operation.into(),
657            timeout_ms,
658            source: None,
659        }
660    }
661
662    /// Create a new timeout error with specific code and source.
663    pub fn timeout_with_source<S: Into<String>, E: std::error::Error + Send + Sync + 'static>(
664        operation: S,
665        timeout_ms: u64,
666        source: E,
667    ) -> Self {
668        Self::Timeout {
669            code: ErrorCode::TimeoutOperation,
670            operation: operation.into(),
671            timeout_ms,
672            source: Some(Box::new(source)),
673        }
674    }
675
676    /// Create a new invalid state error.
677    pub fn invalid_state<S: Into<String>>(message: S) -> Self {
678        Self::InvalidState {
679            code: ErrorCode::InvalidStateValue,
680            message: message.into(),
681            current_state: None,
682            source: None,
683        }
684    }
685
686    /// Create a new invalid state error with current state.
687    pub fn invalid_state_with_current<S: Into<String>, C: Into<String>>(
688        message: S,
689        current_state: C,
690    ) -> Self {
691        Self::InvalidState {
692            code: ErrorCode::InvalidStateTransition,
693            message: message.into(),
694            current_state: Some(current_state.into()),
695            source: None,
696        }
697    }
698
699    /// Create a new invalid state error with specific code.
700    pub fn invalid_state_with_code<S: Into<String>>(code: ErrorCode, message: S) -> Self {
701        Self::InvalidState {
702            code,
703            message: message.into(),
704            current_state: None,
705            source: None,
706        }
707    }
708
709    /// Create a new platform error.
710    pub fn platform<S: Into<String>, P: Into<String>>(message: S, platform: P) -> Self {
711        Self::Platform {
712            code: ErrorCode::PlatformNotSupported,
713            message: message.into(),
714            platform: platform.into(),
715            source: None,
716        }
717    }
718
719    /// Create a new platform error with specific code.
720    pub fn platform_with_code<S: Into<String>, P: Into<String>>(
721        code: ErrorCode,
722        message: S,
723        platform: P,
724    ) -> Self {
725        Self::Platform {
726            code,
727            message: message.into(),
728            platform: platform.into(),
729            source: None,
730        }
731    }
732
733    /// Check if this error is retryable.
734    #[must_use]
735    pub const fn is_retryable(&self) -> bool {
736        matches!(
737            self,
738            Self::Io { .. } | Self::Runtime { .. } | Self::ResourceExhausted { .. }
739        )
740    }
741
742    /// Check if this error is a timeout.
743    #[must_use]
744    pub const fn is_timeout(&self) -> bool {
745        matches!(self, Self::Timeout { .. })
746    }
747
748    /// Check if this error is configuration-related.
749    #[must_use]
750    pub const fn is_config_error(&self) -> bool {
751        matches!(self, Self::Config { .. })
752    }
753
754    /// Get the error category for metrics/logging.
755    #[must_use]
756    pub const fn category(&self) -> &'static str {
757        match self {
758            Self::Config { .. } => "config",
759            Self::Signal { .. } => "signal",
760            Self::Shutdown { .. } => "shutdown",
761            Self::Subsystem { .. } => "subsystem",
762            Self::Io { .. } => "io",
763            Self::Runtime { .. } => "runtime",
764            Self::ResourceExhausted { .. } => "resource",
765            Self::Timeout { .. } => "timeout",
766            Self::InvalidState { .. } => "state",
767            Self::Platform { .. } => "platform",
768        }
769    }
770}
771
772// Implement From for common error types
773impl From<std::io::Error> for Error {
774    fn from(err: std::io::Error) -> Self {
775        Self::io(format!("I/O operation failed: {err}"))
776    }
777}
778
779impl From<ctrlc::Error> for Error {
780    fn from(err: ctrlc::Error) -> Self {
781        Self::signal(format!("Signal handler error: {err}"))
782    }
783}
784
785impl From<figment::Error> for Error {
786    fn from(err: figment::Error) -> Self {
787        Self::config(format!("Configuration loading failed: {err}"))
788    }
789}
790
791#[cfg(feature = "toml")]
792impl From<toml::de::Error> for Error {
793    fn from(err: toml::de::Error) -> Self {
794        Self::config(format!("TOML parsing failed: {err}"))
795    }
796}
797
798#[cfg(feature = "serde_json")]
799impl From<serde_json::Error> for Error {
800    fn from(err: serde_json::Error) -> Self {
801        Self::config(format!("JSON parsing failed: {err}"))
802    }
803}
804
805/// Helper macro for creating errors with formatted messages.
806#[macro_export]
807macro_rules! daemon_error {
808    ($kind:ident, $($arg:tt)*) => {
809        $crate::Error::$kind(format!($($arg)*))
810    };
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    #[test]
818    fn test_error_creation() {
819        let err = Error::config("test message");
820        assert!(err.is_config_error());
821        assert_eq!(err.category(), "config");
822    }
823
824    #[test]
825    fn test_retryable_errors() {
826        let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout");
827        let err = Error::io(format!("operation failed: {io_err}"));
828        assert!(err.is_retryable());
829    }
830
831    #[test]
832    fn test_timeout_error() {
833        let err = Error::timeout("test operation", 5000);
834        assert!(err.is_timeout());
835        assert_eq!(err.category(), "timeout");
836    }
837}