pitchfork_cli/
daemon_status.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIs)]
4#[strum(serialize_all = "snake_case")]
5#[serde(rename_all = "snake_case")]
6#[derive(Default)]
7pub enum DaemonStatus {
8 Failed(String),
9 Waiting,
10 Running,
11 Stopping,
12 Errored(i32),
14 Completed,
26 #[default]
27 Stopped,
28}
29
30impl DaemonStatus {
31 pub fn style(&self) -> String {
32 let s = self.to_string();
33 match self {
34 DaemonStatus::Failed(_) => console::style(s).red().to_string(),
35 DaemonStatus::Waiting => console::style(s).yellow().to_string(),
36 DaemonStatus::Running => console::style(s).green().to_string(),
37 DaemonStatus::Stopping => console::style(s).yellow().to_string(),
38 DaemonStatus::Stopped => console::style(s).dim().to_string(),
39 DaemonStatus::Completed => console::style(s).green().dim().to_string(),
40 DaemonStatus::Errored(_) => console::style(s).red().to_string(),
41 }
42 }
43
44 pub fn error_message(&self) -> Option<String> {
45 match self {
46 DaemonStatus::Failed(msg) => Some(msg.clone()),
47 DaemonStatus::Errored(code) if *code != -1 => Some(format!("exit code {code}")),
48 DaemonStatus::Errored(_) => Some("unknown exit code".to_string()),
49 _ => None,
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 fn all_variants() -> Vec<(&'static str, DaemonStatus)> {
59 vec![
60 ("running", DaemonStatus::Running),
61 ("stopped", DaemonStatus::Stopped),
62 ("waiting", DaemonStatus::Waiting),
63 ("stopping", DaemonStatus::Stopping),
64 ("failed", DaemonStatus::Failed("some error".to_string())),
65 ("errored", DaemonStatus::Errored(1)),
66 ("errored_unknown", DaemonStatus::Errored(-1)),
67 ("completed", DaemonStatus::Completed),
68 ]
69 }
70
71 #[test]
72 fn variants_are_encoded_by_name_not_position() {
73 for (name, status) in all_variants() {
81 let encoded = rmp_serde::to_vec(&status).expect("encode");
82 let name = name.split('_').next().unwrap_or(name);
83 assert!(
84 encoded.windows(name.len()).any(|w| w == name.as_bytes()),
85 "{status} did not encode its name: {encoded:?}"
86 );
87 }
88 }
89
90 #[test]
91 fn test_completed_serializes_as_completed() {
92 assert_eq!(DaemonStatus::Completed.to_string(), "completed");
95 assert_eq!(
96 serde_json::to_string(&DaemonStatus::Completed).unwrap(),
97 "\"completed\""
98 );
99 }
100
101 #[test]
102 fn test_completed_has_no_error_message() {
103 assert!(DaemonStatus::Completed.error_message().is_none());
104 }
105
106 #[test]
107 fn test_daemon_status_json_roundtrip() {
108 for (name, status) in all_variants() {
109 let json_str = serde_json::to_string(&status)
110 .unwrap_or_else(|_| panic!("Failed to serialize {name}"));
111 let result: Result<DaemonStatus, _> = serde_json::from_str(&json_str);
112 assert!(
113 result.is_ok(),
114 "Failed to deserialize {name}: {:?}",
115 result.err()
116 );
117 }
118 }
119
120 #[test]
121 fn test_daemon_status_toml_roundtrip() {
122 #[derive(Serialize, Deserialize, Debug)]
123 struct Wrapper {
124 status: DaemonStatus,
125 }
126
127 for (name, status) in all_variants() {
128 let w = Wrapper { status };
129 let toml_str =
130 toml::to_string(&w).unwrap_or_else(|e| panic!("Failed to serialize {name}: {e}"));
131 let result: Result<Wrapper, _> = toml::from_str(&toml_str);
132 assert!(
133 result.is_ok(),
134 "Failed to deserialize {name}: {:?}\nTOML was: {toml_str:?}",
135 result.err()
136 );
137 }
138 }
139}