1pub type Result<T> = std::result::Result<T, Error>;
99
100#[cfg(feature = "backtrace")]
102pub use std::backtrace::Backtrace;
103
104#[cfg(feature = "backtrace")]
106#[derive(Debug)]
107pub struct BacktraceError {
108 message: String,
110 backtrace: Backtrace,
112 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
114}
115
116#[cfg(feature = "backtrace")]
117impl BacktraceError {
118 #[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 #[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
165#[non_exhaustive]
166pub enum ErrorCode {
167 ConfigInvalid = 1000,
169 ConfigParse = 1001,
170 ConfigMissing = 1002,
171 ConfigTypeMismatch = 1003,
172
173 SignalRegisterFailed = 2000,
175 SignalSendFailed = 2001,
176 SignalInvalid = 2002,
177
178 ShutdownTimeout = 3000,
180 ShutdownAlreadyInProgress = 3001,
181 ShutdownFailed = 3002,
182
183 SubsystemStartFailed = 4000,
185 SubsystemStopFailed = 4001,
186 SubsystemNotFound = 4002,
187 SubsystemAlreadyRegistered = 4003,
188 SubsystemStateInvalid = 4004,
189
190 IoError = 5000,
192 FileNotFound = 5001,
193 FilePermissionDenied = 5002,
194
195 RuntimePanic = 6000,
197 RuntimeAsyncError = 6001,
198 LockFailed = 6002,
199 RuntimeSpawnError = 6003,
200 MissingRuntime = 6004,
201
202 ResourceExhaustedMemory = 7000,
204 ResourceExhaustedCpu = 7001,
205 ResourceExhaustedFileDescriptors = 7002,
206
207 TimeoutOperation = 8000,
209 TimeoutConnection = 8001,
210
211 InvalidStateTransition = 9000,
213 InvalidStateValue = 9001,
214
215 PlatformNotSupported = 10000,
217 PlatformFeatureNotAvailable = 10001,
218
219 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 pub const fn as_str(self) -> &'static str {
232 match self {
233 Self::ConfigInvalid => "CONFIG_INVALID",
235 Self::ConfigParse => "CONFIG_PARSE",
236 Self::ConfigMissing => "CONFIG_MISSING",
237 Self::ConfigTypeMismatch => "CONFIG_TYPE_MISMATCH",
238
239 Self::SignalRegisterFailed => "SIGNAL_REGISTER_FAILED",
241 Self::SignalSendFailed => "SIGNAL_SEND_FAILED",
242 Self::SignalInvalid => "SIGNAL_INVALID",
243
244 Self::ShutdownTimeout => "SHUTDOWN_TIMEOUT",
246 Self::ShutdownAlreadyInProgress => "SHUTDOWN_ALREADY_IN_PROGRESS",
247 Self::ShutdownFailed => "SHUTDOWN_FAILED",
248
249 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 Self::IoError => "IO_ERROR",
258 Self::FileNotFound => "FILE_NOT_FOUND",
259 Self::FilePermissionDenied => "FILE_PERMISSION_DENIED",
260
261 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 Self::ResourceExhaustedMemory => "RESOURCE_EXHAUSTED_MEMORY",
270 Self::ResourceExhaustedCpu => "RESOURCE_EXHAUSTED_CPU",
271 Self::ResourceExhaustedFileDescriptors => "RESOURCE_EXHAUSTED_FILE_DESCRIPTORS",
272
273 Self::TimeoutOperation => "TIMEOUT_OPERATION",
275 Self::TimeoutConnection => "TIMEOUT_CONNECTION",
276
277 Self::InvalidStateTransition => "INVALID_STATE_TRANSITION",
279 Self::InvalidStateValue => "INVALID_STATE_VALUE",
280
281 Self::PlatformNotSupported => "PLATFORM_NOT_SUPPORTED",
283 Self::PlatformFeatureNotAvailable => "PLATFORM_FEATURE_NOT_AVAILABLE",
284
285 Self::Unknown => "UNKNOWN_ERROR",
287 }
288 }
289}
290
291#[cfg_attr(feature = "serde", derive(serde::Serialize))]
293#[derive(thiserror::Error, Debug)]
294pub enum Error {
295 #[error("Configuration error [{code}]: {message}")]
297 Config {
298 code: ErrorCode,
300 message: String,
302 #[source]
304 #[cfg_attr(feature = "serde", serde(skip))]
305 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
306 },
307
308 #[error("Signal handling error [{code}]: {message}{signal:?}")]
310 Signal {
311 code: ErrorCode,
313 message: String,
315 signal: Option<i32>,
317 #[source]
319 #[cfg_attr(feature = "serde", serde(skip))]
320 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
321 },
322
323 #[error("Shutdown error [{code}]: {message}{timeout_ms:?}")]
325 Shutdown {
326 code: ErrorCode,
328 message: String,
330 timeout_ms: Option<u64>,
332 #[source]
334 #[cfg_attr(feature = "serde", serde(skip))]
335 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
336 },
337
338 #[error("Subsystem '{name}' error [{code}]: {message}")]
340 Subsystem {
341 code: ErrorCode,
343 name: String,
345 message: String,
347 #[source]
349 #[cfg_attr(feature = "serde", serde(skip))]
350 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
351 },
352
353 #[error("I/O error [{code}]: {message}")]
355 Io {
356 code: ErrorCode,
358 message: String,
360 #[source]
362 #[cfg_attr(feature = "serde", serde(skip))]
363 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
364 },
365
366 #[error("Resource exhausted [{code}]: {resource} - {message}")]
368 ResourceExhausted {
369 code: ErrorCode,
371 resource: String,
373 message: String,
375 #[source]
377 #[cfg_attr(feature = "serde", serde(skip))]
378 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
379 },
380
381 #[error("Operation timed out [{code}] after {timeout_ms}ms: {operation}")]
383 Timeout {
384 code: ErrorCode,
386 operation: String,
388 timeout_ms: u64,
390 #[source]
392 #[cfg_attr(feature = "serde", serde(skip))]
393 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
394 },
395
396 #[error("Invalid state [{code}]: {message}{current_state:?}")]
398 InvalidState {
399 code: ErrorCode,
401 message: String,
403 current_state: Option<String>,
405 #[source]
407 #[cfg_attr(feature = "serde", serde(skip))]
408 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
409 },
410
411 #[error("Platform error [{code}]: {message} (platform: {platform})")]
413 Platform {
414 code: ErrorCode,
416 message: String,
418 platform: String,
420 #[source]
422 #[cfg_attr(feature = "serde", serde(skip))]
423 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
424 },
425
426 #[error("Runtime error [{code}]: {message}")]
428 Runtime {
429 code: ErrorCode,
431 message: String,
433 #[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 #[cfg(feature = "backtrace")]
444 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[must_use]
744 pub const fn is_timeout(&self) -> bool {
745 matches!(self, Self::Timeout { .. })
746 }
747
748 #[must_use]
750 pub const fn is_config_error(&self) -> bool {
751 matches!(self, Self::Config { .. })
752 }
753
754 #[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
772impl 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#[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}