1use std::{collections::HashMap, io::Result, path::PathBuf, process::Output};
2
3use async_trait::async_trait;
4#[derive(Debug, Clone, Eq, PartialEq)]
10pub struct Command {
11 pub args: Vec<String>,
13 pub cwd: Option<PathBuf>,
15 pub env: Option<HashMap<String, String>>,
17 #[cfg(unix)]
19 pub gid: Option<u32>,
20 pub program: String,
22 #[cfg(unix)]
24 pub uid: Option<u32>,
25}
26
27impl Command {
28 pub fn new<S: Into<String>>(program: S) -> Self {
30 Self {
31 args: vec![],
32 cwd: None,
33 env: None,
34 #[cfg(unix)]
35 gid: None,
36 program: program.into(),
37 #[cfg(unix)]
38 uid: None,
39 }
40 }
41
42 pub fn with_arg<S: Into<String>>(mut self, arg: S) -> Self {
44 self.args.push(arg.into());
45 self
46 }
47
48 pub fn with_args(mut self, args: Vec<String>) -> Self {
50 self.args = args;
51 self
52 }
53
54 pub fn with_cwd<P: Into<PathBuf>>(mut self, cwd: P) -> Self {
56 self.cwd = Some(cwd.into());
57 self
58 }
59
60 pub fn with_env<S: Into<String>>(mut self, key: S, val: S) -> Self {
62 match self.env {
63 Some(ref mut env) => {
64 env.insert(key.into(), val.into());
65 }
66 None => {
67 self.env = Some(HashMap::from_iter([(key.into(), val.into())]));
68 }
69 }
70 self
71 }
72
73 pub fn with_envs(mut self, env: HashMap<String, String>) -> Self {
75 self.env = Some(env);
76 self
77 }
78
79 #[cfg(unix)]
81 pub fn with_gid(mut self, gid: u32) -> Self {
82 self.gid = Some(gid);
83 self
84 }
85
86 #[cfg(unix)]
88 pub fn with_uid(mut self, uid: u32) -> Self {
89 self.uid = Some(uid);
90 self
91 }
92}
93
94#[derive(Debug, Clone, Eq, PartialEq)]
100pub struct CommandOutput {
101 pub code: Option<i32>,
103 pub stderr: Vec<u8>,
105 pub stdout: Vec<u8>,
107}
108
109impl From<Output> for CommandOutput {
110 fn from(output: Output) -> Self {
111 Self {
112 code: output.status.code(),
113 stderr: output.stderr,
114 stdout: output.stdout,
115 }
116 }
117}
118
119#[async_trait]
127pub trait CommandRunner: Send + Sync {
128 async fn run(&self, cmd: &Command) -> Result<CommandOutput>;
130}
131
132pub struct DefaultCommandRunner;
140
141#[async_trait]
142impl CommandRunner for DefaultCommandRunner {
143 async fn run(&self, cmd: &Command) -> Result<CommandOutput> {
144 let mut builder = tokio::process::Command::new(&cmd.program);
145 builder.args(&cmd.args);
146 if let Some(cwd) = &cmd.cwd {
147 builder.current_dir(cwd);
148 }
149 if let Some(env) = &cmd.env {
150 builder.envs(env);
151 }
152 if cfg!(unix) {
153 if let Some(gid) = cmd.gid {
154 builder.gid(gid);
155 }
156 if let Some(uid) = cmd.uid {
157 builder.uid(uid);
158 }
159 }
160 let output = builder.output().await?;
161 Ok(output.into())
162 }
163}
164
165#[cfg(feature = "mock")]
168mockall::mock! {
169 pub CommandRunner {}
175
176 #[async_trait]
177 impl CommandRunner for CommandRunner {
178 async fn run(&self, cmd: &Command) -> Result<CommandOutput>;
179 }
180}