cargo_lambda_interactive/
command.rs1use 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
21pub 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 {}