Skip to main content

forge_core/daemon/
traits.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::str::FromStr;
4use std::time::Duration;
5
6use crate::error::Result;
7
8use super::context::DaemonContext;
9
10/// Trait for long-running daemon handlers.
11pub trait ForgeDaemon: crate::__sealed::Sealed + Send + Sync + 'static {
12    fn info() -> DaemonInfo;
13
14    fn execute(ctx: &DaemonContext) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>;
15}
16
17/// Metadata for a registered daemon handler.
18#[derive(Debug, Clone)]
19pub struct DaemonInfo {
20    pub name: &'static str,
21    pub leader_elected: bool,
22    pub restart_on_panic: bool,
23    pub restart_delay: Duration,
24    pub startup_delay: Duration,
25    pub http_timeout: Option<Duration>,
26    /// `None` means unlimited.
27    pub max_restarts: Option<u32>,
28}
29
30impl Default for DaemonInfo {
31    fn default() -> Self {
32        Self {
33            name: "",
34            leader_elected: true,
35            restart_on_panic: true,
36            restart_delay: Duration::from_secs(5),
37            startup_delay: Duration::from_secs(0),
38            http_timeout: None,
39            max_restarts: None,
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum DaemonStatus {
47    Pending,
48    Acquiring,
49    Running,
50    Stopped,
51    Failed,
52    Restarting,
53}
54
55impl DaemonStatus {
56    pub fn as_str(&self) -> &'static str {
57        match self {
58            Self::Pending => "pending",
59            Self::Acquiring => "acquiring",
60            Self::Running => "running",
61            Self::Stopped => "stopped",
62            Self::Failed => "failed",
63            Self::Restarting => "restarting",
64        }
65    }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct ParseDaemonStatusError(pub String);
70
71impl std::fmt::Display for ParseDaemonStatusError {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "unknown daemon status: {:?}", self.0)
74    }
75}
76
77impl std::error::Error for ParseDaemonStatusError {}
78
79impl FromStr for DaemonStatus {
80    type Err = ParseDaemonStatusError;
81
82    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
83        match s {
84            "pending" => Ok(Self::Pending),
85            "acquiring" => Ok(Self::Acquiring),
86            "running" => Ok(Self::Running),
87            "stopped" => Ok(Self::Stopped),
88            "failed" => Ok(Self::Failed),
89            "restarting" => Ok(Self::Restarting),
90            _ => Err(ParseDaemonStatusError(s.to_string())),
91        }
92    }
93}
94
95#[cfg(test)]
96#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_default_daemon_info() {
102        let info = DaemonInfo::default();
103        assert!(info.leader_elected);
104        assert!(info.restart_on_panic);
105        assert_eq!(info.restart_delay, Duration::from_secs(5));
106        assert_eq!(info.startup_delay, Duration::from_secs(0));
107        assert_eq!(info.http_timeout, None);
108        assert!(info.max_restarts.is_none());
109    }
110
111    #[test]
112    fn test_status_conversion() {
113        assert_eq!(DaemonStatus::Running.as_str(), "running");
114        assert_eq!("running".parse::<DaemonStatus>(), Ok(DaemonStatus::Running));
115        assert_eq!(DaemonStatus::Failed.as_str(), "failed");
116        assert_eq!("failed".parse::<DaemonStatus>(), Ok(DaemonStatus::Failed));
117    }
118}