1use std::{fmt::Display, str::FromStr};
2
3use bollard::secret::{ContainerInspectResponse, ContainerStateStatusEnum};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum State {
8 Created,
9 Dead,
10 Exited,
11 Paused,
12 Removing,
13 Restarting,
14 Running,
15}
16
17#[derive(Debug, thiserror::Error, PartialEq)]
18pub enum GetStateError {
19 #[error("Missing state")]
20 MissingState,
21 #[error(transparent)]
22 FromContainerStateStatusEnum(#[from] FromContainerStateStatusEnumError),
23}
24
25impl TryFrom<&ContainerInspectResponse> for State {
26 type Error = GetStateError;
27
28 fn try_from(value: &ContainerInspectResponse) -> Result<Self, Self::Error> {
29 let state = &value
30 .state
31 .as_ref()
32 .and_then(|s| s.status)
33 .ok_or(GetStateError::MissingState)?;
34 Ok(state.try_into()?)
35 }
36}
37
38#[derive(Debug, thiserror::Error, PartialEq)]
39pub enum FromContainerStateStatusEnumError {
40 #[error("Empty state")]
41 EmptyState,
42}
43
44impl TryFrom<&ContainerStateStatusEnum> for State {
45 type Error = FromContainerStateStatusEnumError;
46
47 fn try_from(value: &ContainerStateStatusEnum) -> Result<Self, Self::Error> {
48 Ok(match value {
49 ContainerStateStatusEnum::CREATED => State::Created,
50 ContainerStateStatusEnum::DEAD => State::Dead,
51 ContainerStateStatusEnum::EXITED => State::Exited,
52 ContainerStateStatusEnum::PAUSED => State::Paused,
53 ContainerStateStatusEnum::REMOVING => State::Removing,
54 ContainerStateStatusEnum::RESTARTING => State::Restarting,
55 ContainerStateStatusEnum::RUNNING => State::Running,
56
57 ContainerStateStatusEnum::EMPTY => {
58 return Err(FromContainerStateStatusEnumError::EmptyState);
59 }
60 })
61 }
62}
63
64impl Display for State {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 match self {
67 State::Created => write!(f, "created"),
68 State::Dead => write!(f, "dead"),
69 State::Exited => write!(f, "exited"),
70 State::Paused => write!(f, "paused"),
71 State::Removing => write!(f, "removing"),
72 State::Restarting => write!(f, "restarting"),
73 State::Running => write!(f, "running"),
74 }
75 }
76}
77
78#[derive(Debug, thiserror::Error, PartialEq)]
79pub enum FromStrStateError {
80 #[error("Invalid state: {0}")]
81 InvalidState(String),
82}
83
84impl FromStr for State {
85 type Err = FromStrStateError;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 match s {
89 "created" => Ok(State::Created),
90 "dead" => Ok(State::Dead),
91 "exited" => Ok(State::Exited),
92 "paused" => Ok(State::Paused),
93 "removing" => Ok(State::Removing),
94 "restarting" => Ok(State::Restarting),
95 "running" => Ok(State::Running),
96 _ => Err(FromStrStateError::InvalidState(s.to_string())),
97 }
98 }
99}
100
101#[cfg(feature = "serde")]
102impl serde::Serialize for State {
103 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104 where
105 S: serde::Serializer,
106 {
107 serializer.serialize_str(&self.to_string())
108 }
109}
110
111#[cfg(feature = "serde")]
112impl<'de> serde::Deserialize<'de> for State {
113 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114 where
115 D: serde::Deserializer<'de>,
116 {
117 let s = String::deserialize(deserializer)?;
118 State::from_str(&s).map_err(serde::de::Error::custom)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_try_from_container_state_status_enum_success() {
128 assert_eq!(
130 State::try_from(&ContainerStateStatusEnum::CREATED).unwrap(),
131 State::Created
132 );
133 assert_eq!(
134 State::try_from(&ContainerStateStatusEnum::DEAD).unwrap(),
135 State::Dead
136 );
137 assert_eq!(
138 State::try_from(&ContainerStateStatusEnum::EXITED).unwrap(),
139 State::Exited
140 );
141 assert_eq!(
142 State::try_from(&ContainerStateStatusEnum::PAUSED).unwrap(),
143 State::Paused
144 );
145 assert_eq!(
146 State::try_from(&ContainerStateStatusEnum::REMOVING).unwrap(),
147 State::Removing
148 );
149 assert_eq!(
150 State::try_from(&ContainerStateStatusEnum::RESTARTING).unwrap(),
151 State::Restarting
152 );
153 assert_eq!(
154 State::try_from(&ContainerStateStatusEnum::RUNNING).unwrap(),
155 State::Running
156 );
157 }
158
159 #[test]
160 fn test_try_from_container_state_status_enum_empty_error() {
161 let result = State::try_from(&ContainerStateStatusEnum::EMPTY);
163 assert!(result.is_err());
164 assert!(matches!(
165 result.unwrap_err(),
166 FromContainerStateStatusEnumError::EmptyState
167 ));
168 }
169
170 #[test]
171 fn test_try_from_container_inspect_response_success() {
172 use bollard::secret::ContainerState;
173
174 let container_state = ContainerState {
176 status: Some(ContainerStateStatusEnum::RUNNING),
177 ..Default::default()
178 };
179 let container_inspect = ContainerInspectResponse {
180 state: Some(container_state),
181 ..Default::default()
182 };
183
184 let result = State::try_from(&container_inspect);
185 assert!(result.is_ok());
186 assert_eq!(result.unwrap(), State::Running);
187 }
188
189 #[test]
190 fn test_try_from_container_inspect_response_missing_state() {
191 let container_inspect = ContainerInspectResponse::default();
193
194 let result = State::try_from(&container_inspect);
195 assert!(result.is_err());
196 assert!(matches!(result.unwrap_err(), GetStateError::MissingState));
197 }
198
199 #[test]
200 fn test_try_from_container_inspect_response_missing_status() {
201 use bollard::secret::ContainerState;
202
203 let container_inspect = ContainerInspectResponse {
205 state: Some(ContainerState::default()), ..Default::default()
207 };
208
209 let result = State::try_from(&container_inspect);
210 assert!(result.is_err());
211 assert!(matches!(result.unwrap_err(), GetStateError::MissingState));
212 }
213
214 #[test]
215 fn test_try_from_container_inspect_response_empty_status() {
216 use bollard::secret::ContainerState;
217
218 let container_state = ContainerState {
220 status: Some(ContainerStateStatusEnum::EMPTY),
221 ..Default::default()
222 };
223 let container_inspect = ContainerInspectResponse {
224 state: Some(container_state),
225 ..Default::default()
226 };
227
228 let result = State::try_from(&container_inspect);
229 assert!(result.is_err());
230 assert!(matches!(
231 result.unwrap_err(),
232 GetStateError::FromContainerStateStatusEnum(_)
233 ));
234 }
235
236 #[test]
237 fn test_all_states_have_corresponding_enum_values() {
238 let test_cases = [
240 (ContainerStateStatusEnum::CREATED, State::Created),
241 (ContainerStateStatusEnum::DEAD, State::Dead),
242 (ContainerStateStatusEnum::EXITED, State::Exited),
243 (ContainerStateStatusEnum::PAUSED, State::Paused),
244 (ContainerStateStatusEnum::REMOVING, State::Removing),
245 (ContainerStateStatusEnum::RESTARTING, State::Restarting),
246 (ContainerStateStatusEnum::RUNNING, State::Running),
247 ];
248
249 for (enum_value, expected_state) in test_cases {
250 let result = State::try_from(&enum_value);
251 assert!(result.is_ok(), "Failed to convert {:?}", enum_value);
252 assert_eq!(result.unwrap(), expected_state);
253 }
254 }
255
256 #[test]
257 fn test_state_debug_and_clone() {
258 let state = State::Running;
260 let cloned_state = state;
261 assert_eq!(state, cloned_state);
262
263 let debug_output = format!("{:?}", state);
265 assert_eq!(debug_output, "Running");
266 }
267
268 #[test]
269 fn test_error_types_debug() {
270 let get_state_error = GetStateError::MissingState;
272 let debug_output = format!("{:?}", get_state_error);
273 assert!(debug_output.contains("MissingState"));
274
275 let from_enum_error = FromContainerStateStatusEnumError::EmptyState;
276 let debug_output = format!("{:?}", from_enum_error);
277 assert!(debug_output.contains("EmptyState"));
278 }
279
280 #[test]
281 fn test_display_all_states() {
282 assert_eq!(State::Created.to_string(), "created");
284 assert_eq!(State::Dead.to_string(), "dead");
285 assert_eq!(State::Exited.to_string(), "exited");
286 assert_eq!(State::Paused.to_string(), "paused");
287 assert_eq!(State::Removing.to_string(), "removing");
288 assert_eq!(State::Restarting.to_string(), "restarting");
289 assert_eq!(State::Running.to_string(), "running");
290 }
291
292 #[test]
293 fn test_from_str_success() {
294 assert_eq!("created".parse::<State>().unwrap(), State::Created);
296 assert_eq!("dead".parse::<State>().unwrap(), State::Dead);
297 assert_eq!("exited".parse::<State>().unwrap(), State::Exited);
298 assert_eq!("paused".parse::<State>().unwrap(), State::Paused);
299 assert_eq!("removing".parse::<State>().unwrap(), State::Removing);
300 assert_eq!("restarting".parse::<State>().unwrap(), State::Restarting);
301 assert_eq!("running".parse::<State>().unwrap(), State::Running);
302 }
303
304 #[test]
305 fn test_from_str_invalid() {
306 let result = "invalid".parse::<State>();
308 assert!(result.is_err());
309 assert_eq!(
310 result.unwrap_err(),
311 FromStrStateError::InvalidState("invalid".to_string())
312 );
313 }
314}