1use serde_json::Value;
2use thiserror::Error;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct ContainerSummary {
6 pub id: String,
7 pub name: Option<String>,
8 pub status: Option<String>,
9 pub running: bool,
10}
11
12impl ContainerSummary {
13 pub fn matches(&self, needle: &str) -> bool {
14 self.id == needle || self.name.as_deref() == Some(needle)
15 }
16}
17
18#[derive(Debug, Error)]
19pub enum StateError {
20 #[error("failed to parse container list output: {0}")]
21 ParseList(#[source] serde_json::Error),
22}
23
24pub fn parse_list_output(output: &str) -> Result<Vec<ContainerSummary>, StateError> {
25 let value = serde_json::from_str::<Value>(output).map_err(StateError::ParseList)?;
26 Ok(match value {
27 Value::Array(items) => items.into_iter().filter_map(parse_summary).collect(),
28 Value::Object(_) => parse_summary(value).into_iter().collect(),
29 _ => Vec::new(),
30 })
31}
32
33pub fn parse_inspect_output(output: &str) -> Option<Value> {
34 let value = serde_json::from_str::<Value>(output).ok()?;
35 match value {
36 Value::Array(items) => items.into_iter().next(),
37 other => Some(other),
38 }
39}
40
41fn parse_summary(value: Value) -> Option<ContainerSummary> {
42 let id = field(&value, &["id", "ID", "identifier", "name"])?;
43 let name = field(&value, &["name", "Name"]);
44 let status = field(&value, &["status", "Status", "state", "State"]);
45 let running = value
46 .get("running")
47 .and_then(Value::as_bool)
48 .or_else(|| {
49 status
50 .as_ref()
51 .map(|value| value.eq_ignore_ascii_case("running"))
52 })
53 .unwrap_or(false);
54 Some(ContainerSummary {
55 id,
56 name,
57 status,
58 running,
59 })
60}
61
62fn field(value: &Value, names: &[&str]) -> Option<String> {
63 names.iter().find_map(|name| {
64 value.get(name).and_then(|value| match value {
65 Value::String(value) => Some(value.clone()),
66 Value::Number(value) => Some(value.to_string()),
67 _ => None,
68 })
69 })
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn list_parse_error_bubbles_up() {
78 let error = parse_list_output("not-json").unwrap_err();
79 assert!(
80 error
81 .to_string()
82 .contains("failed to parse container list output")
83 );
84 }
85}