cargo_lambda_interactive/
command.rs

1use miette::{Diagnostic, Result};
2use std::process::Stdio;
3use tokio::{
4    io::AsyncReadExt,
5    process::{Child, Command},
6};
7
8#[cfg(target_os = "windows")]
9pub fn new_command(cmd: &str) -> Command {
10    let mut command = Command::new("cmd.exe");
11    command.arg("/c");
12    command.arg(cmd);
13    command
14}
15
16#[cfg(not(target_os = "windows"))]
17pub fn new_command(cmd: &str) -> Command {
18    Command::new(cmd)
19}
20
21/// Run a command without producing any output in STDOUT and STDERR
22pub async fn silent_command(cmd: &str, args: &[&str]) -> Result<(), CommandError> {
23    let child = new_command(cmd)
24        .args(args)
25        .stdout(Stdio::piped())
26        .stderr(Stdio::piped())
27        .spawn();
28    let Ok(mut child) = child else {
29        return Err(capture_error(cmd, args, None, Some(child.err().unwrap())).await);
30    };
31
32    let result = child.wait().await;
33    let Ok(result) = result else {
34        return Err(capture_error(cmd, args, Some(&mut child), None).await);
35    };
36
37    tracing::trace!(%result);
38
39    if result.success() {
40        Ok(())
41    } else {
42        Err(capture_error(cmd, args, Some(&mut child), None).await)
43    }
44}
45
46async fn capture_error(
47    cmd: &str,
48    args: &[&str],
49    child: Option<&mut Child>,
50    error: Option<std::io::Error>,
51) -> CommandError {
52    let mut stdout = Vec::new();
53    let mut stderr = Vec::new();
54
55    if let Some(child) = child {
56        let mut reader = child.stdout.take().expect("stdout is not captured");
57        reader
58            .read_to_end(&mut stdout)
59            .await
60            .expect("Failed to read stdout");
61
62        let mut reader = child.stderr.take().expect("stderr is not captured");
63        reader
64            .read_to_end(&mut stderr)
65            .await
66            .expect("Failed to read stderr");
67    }
68
69    CommandError {
70        command: format!("{} {}", cmd, args.join(" ")),
71        stdout,
72        stderr,
73        error,
74    }
75}
76
77#[derive(Debug, Default, Diagnostic)]
78#[diagnostic(code(command_error))]
79pub struct CommandError {
80    command: String,
81    stdout: Vec<u8>,
82    stderr: Vec<u8>,
83    error: Option<std::io::Error>,
84}
85
86impl std::fmt::Display for CommandError {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        write!(f, "Command `{}` failed", self.command)?;
89
90        if let Some(error) = &self.error {
91            write!(f, ": {}", error)?;
92        }
93
94        if !self.stdout.is_empty() {
95            write!(f, "\n{}", String::from_utf8_lossy(&self.stdout))?;
96        }
97
98        if !self.stderr.is_empty() {
99            write!(f, "\n{}", String::from_utf8_lossy(&self.stderr))?;
100        }
101
102        Ok(())
103    }
104}
105
106impl std::error::Error for CommandError {}