use std::{fmt::Display, str::FromStr};
use bollard::models::{ContainerInspectResponse, ContainerStateStatusEnum};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum State {
Created,
Dead,
Exited,
Paused,
Removing,
Restarting,
Running,
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum GetStateError {
#[error("Missing state")]
MissingState,
#[error(transparent)]
FromContainerStateStatusEnum(#[from] FromContainerStateStatusEnumError),
}
impl TryFrom<&ContainerInspectResponse> for State {
type Error = GetStateError;
fn try_from(value: &ContainerInspectResponse) -> Result<Self, Self::Error> {
let state = &value
.state
.as_ref()
.and_then(|s| s.status)
.ok_or(GetStateError::MissingState)?;
Ok(state.try_into()?)
}
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum FromContainerStateStatusEnumError {
#[error("Empty state")]
EmptyState,
}
impl TryFrom<&ContainerStateStatusEnum> for State {
type Error = FromContainerStateStatusEnumError;
fn try_from(value: &ContainerStateStatusEnum) -> Result<Self, Self::Error> {
Ok(match value {
ContainerStateStatusEnum::CREATED => State::Created,
ContainerStateStatusEnum::DEAD => State::Dead,
ContainerStateStatusEnum::EXITED => State::Exited,
ContainerStateStatusEnum::PAUSED => State::Paused,
ContainerStateStatusEnum::REMOVING => State::Removing,
ContainerStateStatusEnum::RESTARTING => State::Restarting,
ContainerStateStatusEnum::RUNNING => State::Running,
ContainerStateStatusEnum::EMPTY => {
return Err(FromContainerStateStatusEnumError::EmptyState);
}
ContainerStateStatusEnum::STOPPING => State::Exited,
})
}
}
impl Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::Created => write!(f, "created"),
State::Dead => write!(f, "dead"),
State::Exited => write!(f, "exited"),
State::Paused => write!(f, "paused"),
State::Removing => write!(f, "removing"),
State::Restarting => write!(f, "restarting"),
State::Running => write!(f, "running"),
}
}
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum FromStrStateError {
#[error("Invalid state: {0}")]
InvalidState(String),
}
impl FromStr for State {
type Err = FromStrStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"created" => Ok(State::Created),
"dead" => Ok(State::Dead),
"exited" => Ok(State::Exited),
"paused" => Ok(State::Paused),
"removing" => Ok(State::Removing),
"restarting" => Ok(State::Restarting),
"running" => Ok(State::Running),
_ => Err(FromStrStateError::InvalidState(s.to_string())),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for State {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for State {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
State::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_from_container_state_status_enum_success() {
assert_eq!(
State::try_from(&ContainerStateStatusEnum::CREATED).unwrap(),
State::Created
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::DEAD).unwrap(),
State::Dead
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::EXITED).unwrap(),
State::Exited
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::PAUSED).unwrap(),
State::Paused
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::REMOVING).unwrap(),
State::Removing
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::RESTARTING).unwrap(),
State::Restarting
);
assert_eq!(
State::try_from(&ContainerStateStatusEnum::RUNNING).unwrap(),
State::Running
);
}
#[test]
fn test_try_from_container_state_status_enum_empty_error() {
let result = State::try_from(&ContainerStateStatusEnum::EMPTY);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
FromContainerStateStatusEnumError::EmptyState
));
}
#[test]
fn test_try_from_container_inspect_response_success() {
use bollard::models::ContainerState;
let container_state = ContainerState {
status: Some(ContainerStateStatusEnum::RUNNING),
..Default::default()
};
let container_inspect = ContainerInspectResponse {
state: Some(container_state),
..Default::default()
};
let result = State::try_from(&container_inspect);
assert!(result.is_ok());
assert_eq!(result.unwrap(), State::Running);
}
#[test]
fn test_try_from_container_inspect_response_missing_state() {
let container_inspect = ContainerInspectResponse::default();
let result = State::try_from(&container_inspect);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), GetStateError::MissingState));
}
#[test]
fn test_try_from_container_inspect_response_missing_status() {
use bollard::models::ContainerState;
let container_inspect = ContainerInspectResponse {
state: Some(ContainerState::default()), ..Default::default()
};
let result = State::try_from(&container_inspect);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), GetStateError::MissingState));
}
#[test]
fn test_try_from_container_inspect_response_empty_status() {
use bollard::models::ContainerState;
let container_state = ContainerState {
status: Some(ContainerStateStatusEnum::EMPTY),
..Default::default()
};
let container_inspect = ContainerInspectResponse {
state: Some(container_state),
..Default::default()
};
let result = State::try_from(&container_inspect);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
GetStateError::FromContainerStateStatusEnum(_)
));
}
#[test]
fn test_all_states_have_corresponding_enum_values() {
let test_cases = [
(ContainerStateStatusEnum::CREATED, State::Created),
(ContainerStateStatusEnum::DEAD, State::Dead),
(ContainerStateStatusEnum::EXITED, State::Exited),
(ContainerStateStatusEnum::PAUSED, State::Paused),
(ContainerStateStatusEnum::REMOVING, State::Removing),
(ContainerStateStatusEnum::RESTARTING, State::Restarting),
(ContainerStateStatusEnum::RUNNING, State::Running),
];
for (enum_value, expected_state) in test_cases {
let result = State::try_from(&enum_value);
assert!(result.is_ok(), "Failed to convert {:?}", enum_value);
assert_eq!(result.unwrap(), expected_state);
}
}
#[test]
fn test_state_debug_and_clone() {
let state = State::Running;
let cloned_state = state;
assert_eq!(state, cloned_state);
let debug_output = format!("{:?}", state);
assert_eq!(debug_output, "Running");
}
#[test]
fn test_error_types_debug() {
let get_state_error = GetStateError::MissingState;
let debug_output = format!("{:?}", get_state_error);
assert!(debug_output.contains("MissingState"));
let from_enum_error = FromContainerStateStatusEnumError::EmptyState;
let debug_output = format!("{:?}", from_enum_error);
assert!(debug_output.contains("EmptyState"));
}
#[test]
fn test_display_all_states() {
assert_eq!(State::Created.to_string(), "created");
assert_eq!(State::Dead.to_string(), "dead");
assert_eq!(State::Exited.to_string(), "exited");
assert_eq!(State::Paused.to_string(), "paused");
assert_eq!(State::Removing.to_string(), "removing");
assert_eq!(State::Restarting.to_string(), "restarting");
assert_eq!(State::Running.to_string(), "running");
}
#[test]
fn test_from_str_success() {
assert_eq!("created".parse::<State>().unwrap(), State::Created);
assert_eq!("dead".parse::<State>().unwrap(), State::Dead);
assert_eq!("exited".parse::<State>().unwrap(), State::Exited);
assert_eq!("paused".parse::<State>().unwrap(), State::Paused);
assert_eq!("removing".parse::<State>().unwrap(), State::Removing);
assert_eq!("restarting".parse::<State>().unwrap(), State::Restarting);
assert_eq!("running".parse::<State>().unwrap(), State::Running);
}
#[test]
fn test_from_str_invalid() {
let result = "invalid".parse::<State>();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
FromStrStateError::InvalidState("invalid".to_string())
);
}
}