command_json.rs

 1use std::process::Output;
 2
 3use async_trait::async_trait;
 4use serde::Deserialize;
 5use util::command::Command;
 6
 7use crate::devcontainer_api::DevContainerError;
 8
 9pub(crate) struct DefaultCommandRunner;
10
11impl DefaultCommandRunner {
12    pub(crate) fn new() -> Self {
13        Self
14    }
15}
16
17#[async_trait]
18impl CommandRunner for DefaultCommandRunner {
19    async fn run_command(&self, command: &mut Command) -> Result<Output, std::io::Error> {
20        command.output().await
21    }
22}
23
24#[async_trait]
25pub(crate) trait CommandRunner: Send + Sync {
26    async fn run_command(&self, command: &mut Command) -> Result<Output, std::io::Error>;
27}
28
29pub(crate) async fn evaluate_json_command<T>(
30    mut command: Command,
31) -> Result<Option<T>, DevContainerError>
32where
33    T: for<'de> Deserialize<'de>,
34{
35    let output = command.output().await.map_err(|e| {
36        log::error!("Error running command {:?}: {e}", command);
37        DevContainerError::CommandFailed(command.get_program().display().to_string())
38    })?;
39
40    deserialize_json_output(output).map_err(|e| {
41        log::error!("Error running command {:?}: {e}", command);
42        DevContainerError::CommandFailed(command.get_program().display().to_string())
43    })
44}
45
46pub(crate) fn deserialize_json_output<T>(output: Output) -> Result<Option<T>, String>
47where
48    T: for<'de> Deserialize<'de>,
49{
50    if output.status.success() {
51        let raw = String::from_utf8_lossy(&output.stdout);
52        if raw.is_empty() || raw.trim() == "[]" || raw.trim() == "{}" {
53            return Ok(None);
54        }
55        let value = serde_json_lenient::from_str(&raw)
56            .map_err(|e| format!("Error deserializing from raw json: {e}"));
57        value
58    } else {
59        let std_err = String::from_utf8_lossy(&output.stderr);
60        Err(format!(
61            "Sent non-successful output; cannot deserialize. StdErr: {std_err}"
62        ))
63    }
64}