Skip to main content

shep_core/
status.rs

1//! Process lifecycle status
2
3use core::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Lifecycle state of a sheep (one managed process)
8///
9/// The serialized strings are the wire contract; `waiting-restart` means a
10/// backoff or restart delay is pending.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "kebab-case")]
13pub enum ProcStatus {
14    /// Spawned, not yet ready
15    Starting,
16    /// Running and (if configured) ready
17    Online,
18    /// This instance is going away and is not a restart target
19    ///
20    /// Set by whichever step marks the drainee before its replacement takes
21    /// the slot: `SpawnNew` for an overlap reload, `DrainOld` for a serial
22    /// one. Either way, the old and new instance never both count as
23    /// running. An operator's `stop` leaves a sheep `Online` through its
24    /// whole kill ladder instead; a scheduled or out-of-band restart must
25    /// reject a sheep in this status rather than race the replacement
26    /// taking over its slot.
27    Stopping,
28    /// Cleanly stopped; not scheduled to run
29    Stopped,
30    /// Restart budget exhausted or spawn failed
31    Errored,
32    /// Restart pending after a backoff or configured delay
33    WaitingRestart,
34}
35
36impl ProcStatus {
37    /// The status word, exactly as [`fmt::Display`] and the JSON encoding
38    /// spell it.
39    const fn as_str(&self) -> &'static str {
40        match self {
41            Self::Starting => "starting",
42            Self::Online => "online",
43            Self::Stopping => "stopping",
44            Self::Stopped => "stopped",
45            Self::Errored => "errored",
46            Self::WaitingRestart => "waiting-restart",
47        }
48    }
49}
50
51/// The status word, for a caller putting it in a cell or a payload without
52/// going through [`fmt::Display`] and an allocation. By value, since
53/// `ProcStatus` is `Copy`.
54impl From<ProcStatus> for &'static str {
55    fn from(source: ProcStatus) -> Self {
56        source.as_str()
57    }
58}
59
60impl fmt::Display for ProcStatus {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        f.write_str(self.as_str())
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn wire_strings_are_stable() {
72        let cases = [
73            (ProcStatus::Starting, "\"starting\""),
74            (ProcStatus::Online, "\"online\""),
75            (ProcStatus::Stopping, "\"stopping\""),
76            (ProcStatus::Stopped, "\"stopped\""),
77            (ProcStatus::Errored, "\"errored\""),
78            (ProcStatus::WaitingRestart, "\"waiting-restart\""),
79        ];
80        for (status, json) in cases {
81            assert_eq!(serde_json::to_string(&status).unwrap(), json);
82            assert_eq!(serde_json::from_str::<ProcStatus>(json).unwrap(), status);
83            assert_eq!(format!("\"{status}\""), json);
84        }
85    }
86}