solti_model/domain/policy/
restart.rs1use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8use crate::error::{ModelError, ModelResult};
9
10#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(tag = "type", rename_all = "camelCase")]
29#[non_exhaustive]
30pub enum RestartPolicy {
31 #[default]
33 Never,
34 OnFailure,
36 #[serde(rename_all = "camelCase")]
48 Always {
49 #[serde(skip_serializing_if = "Option::is_none")]
50 interval_ms: Option<u64>,
51 },
52}
53
54impl RestartPolicy {
55 pub const fn always() -> Self {
57 RestartPolicy::Always { interval_ms: None }
58 }
59
60 pub const fn periodic(interval_ms: u64) -> Self {
62 RestartPolicy::Always {
63 interval_ms: Some(interval_ms),
64 }
65 }
66}
67
68impl FromStr for RestartPolicy {
69 type Err = ModelError;
70
71 fn from_str(s: &str) -> ModelResult<Self> {
72 let original = s.trim();
73 if original.is_empty() {
74 return Ok(RestartPolicy::Never);
75 }
76
77 let (head, rest) = match original.find(':') {
78 Some(pos) => (&original[..pos], Some(original[pos + 1..].trim())),
79 None => (original, None),
80 };
81
82 if head.eq_ignore_ascii_case("never") {
83 Ok(RestartPolicy::Never)
84 } else if head.eq_ignore_ascii_case("on-failure") || head.eq_ignore_ascii_case("failure") {
85 Ok(RestartPolicy::OnFailure)
86 } else if head.eq_ignore_ascii_case("always") {
87 let interval_ms = match rest {
88 None | Some("") => None,
89 Some(v) => {
90 let v = v.parse::<u64>().map_err(|_| {
91 ModelError::UnknownRestart(format!(
92 "invalid interval in '{}': must be u64",
93 original
94 ))
95 })?;
96 Some(v)
97 }
98 };
99 Ok(RestartPolicy::Always { interval_ms })
100 } else {
101 Err(ModelError::UnknownRestart(original.to_string()))
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::RestartPolicy;
109 use crate::error::ModelError;
110 use std::str::FromStr;
111
112 #[test]
113 fn parse_never_and_empty() {
114 assert_eq!(RestartPolicy::from_str("").unwrap(), RestartPolicy::Never);
115 assert_eq!(
116 RestartPolicy::from_str("never").unwrap(),
117 RestartPolicy::Never
118 );
119 assert_eq!(
120 RestartPolicy::from_str(" NeVeR ").unwrap(),
121 RestartPolicy::Never
122 );
123 }
124
125 #[test]
126 fn parse_on_failure() {
127 assert_eq!(
128 RestartPolicy::from_str("on-failure").unwrap(),
129 RestartPolicy::OnFailure
130 );
131 assert_eq!(
132 RestartPolicy::from_str("failure").unwrap(),
133 RestartPolicy::OnFailure
134 );
135 assert_eq!(
136 RestartPolicy::from_str(" Failure ").unwrap(),
137 RestartPolicy::OnFailure
138 );
139 }
140
141 #[test]
142 fn parse_always_immediate() {
143 assert_eq!(
144 RestartPolicy::from_str("always").unwrap(),
145 RestartPolicy::Always { interval_ms: None }
146 );
147 assert_eq!(
148 RestartPolicy::from_str(" ALWAYS ").unwrap(),
149 RestartPolicy::Always { interval_ms: None }
150 );
151 assert_eq!(
152 RestartPolicy::from_str("always:").unwrap(),
153 RestartPolicy::Always { interval_ms: None }
154 );
155 assert_eq!(
156 RestartPolicy::from_str("always: ").unwrap(),
157 RestartPolicy::Always { interval_ms: None }
158 );
159 }
160
161 #[test]
162 fn parse_always_with_interval() {
163 assert_eq!(
164 RestartPolicy::from_str("always:1000").unwrap(),
165 RestartPolicy::Always {
166 interval_ms: Some(1000)
167 }
168 );
169 assert_eq!(
170 RestartPolicy::from_str(" Always: 60000 ").unwrap(),
171 RestartPolicy::Always {
172 interval_ms: Some(60000)
173 }
174 );
175 }
176
177 #[test]
178 fn parse_always_invalid_interval() {
179 let err = RestartPolicy::from_str("always:not-a-number").unwrap_err();
180 assert!(matches!(err, ModelError::UnknownRestart(_)));
181 }
182
183 #[test]
184 fn parse_unknown_head_fails() {
185 let err = RestartPolicy::from_str("random").unwrap_err();
186 assert!(matches!(err, ModelError::UnknownRestart(_)));
187 }
188}