cmd_utils/
cmd_spawn.rs

1use std::process::Command;
2use std::io;
3use std::fmt;
4
5/// Child process error
6#[derive(Debug)]
7pub struct ChildError {
8    /// Name of the child program, see [std::process::Command.get_program()](https://doc.rust-lang.org/std/process/struct.Command.html#method.get_program)
9    pub program: String,
10    /// Exit code of the child process see [std::process::ExitStatus.code()](https://doc.rust-lang.org/std/process/struct.ExitStatus.html#method.code)
11    pub code: Option<i32>
12}
13
14impl fmt::Display for ChildError {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(
17            f,
18            "program `{}` failed with status code {}",
19            self.program,
20            match self.code {
21                Some(code) => code.to_string(),
22                None => String::from("unknown")
23            }
24        )
25    }
26}
27
28/// Error result of a command spawn
29#[derive(Debug)]
30pub enum CmdSpawnError {
31    /// command spawn std::io error [std::io::Error](https://doc.rust-lang.org/std/io/struct.Error.html)
32    IO(io::Error),
33    /// Child process exited with error
34    Child(ChildError)
35}
36
37impl fmt::Display for CmdSpawnError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match &*self {
40            CmdSpawnError::IO(e) => write!(f, "command IO error {}", e),
41            CmdSpawnError::Child(e) => write!(f, "child {}", e),
42        }
43    }
44}
45
46impl From<CmdSpawnError> for io::Error {
47    fn from(val: CmdSpawnError) -> Self {
48        match val {
49            CmdSpawnError::IO(e) => e,
50            CmdSpawnError::Child(e) => {
51                io::Error::new(io::ErrorKind::Other, format!("{}", e))
52            },
53        }
54    }
55}
56
57/// `spawn` and `wait` command
58///
59/// # Errors
60///
61/// command_spawn can result in `CmdSpawnError`:
62/// - `CmdSpawnError::IO(std::io::Error)` when `spawn` or `wait` fail
63/// - `CmdSpawnError::Child(ChildError)` when the child process exit with a failed status
64pub fn command_spawn (command: &mut Command) -> Result<(), CmdSpawnError> {
65    let process = command.spawn();
66    match process {
67        Ok(mut child) => {
68            match child.wait() {
69                Ok(status) => {
70                    if !status.success() {
71                        return Err(
72                            CmdSpawnError::Child(
73                                ChildError {
74                                    program: command.get_program()
75                                        .to_str()
76                                        .unwrap_or("unknwown")
77                                        .to_string(),
78                                    code: status.code()
79                                }
80                            )
81                        )
82                    }
83                    return Ok(())
84                },
85                Err(e) => return Err(CmdSpawnError::IO(e))
86            };
87        },
88        Err(e) => return Err(CmdSpawnError::IO(e))
89    }
90}
91
92pub trait CmdRun {
93    /// `spawn` and `wait` child process
94    ///
95    /// # Errors
96    ///
97    /// command.run() can result in `CmdSpawnError`:
98    /// - `CmdSpawnError::IO(std::io::Error)` when `spawn` or `wait` fail
99    /// - `CmdSpawnError::Child(ChildError)` when the child process exit with a failed status
100    fn run(&mut self) -> Result<(), CmdSpawnError>;
101}
102
103impl CmdRun for Command {
104    fn run(&mut self) -> Result<(), CmdSpawnError> {
105        command_spawn(self)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::CmdRun;
112    use std::process::Command;
113
114    #[test]
115    fn cmd_spawn_success() {
116        let mut cmd = Command::new("test");
117        match cmd.args([
118            "-n",
119            "a"
120        ]).run() {
121            Ok(_) => (),
122            Err(e) => panic!("{}", e),
123        };
124    }
125
126    #[test]
127    fn cmd_spawn_child_error() {
128        let program = "test";
129        let mut cmd = Command::new(&"test");
130        match cmd.args([
131            "-n",
132            ""
133        ]).run() {
134            Ok(_) => panic!("should not have succeded"),
135            Err(e) => {
136                match e {
137                    crate::CmdSpawnError::IO(e) => panic!("{}", e),
138                    crate::CmdSpawnError::Child(e) => {
139                        assert_eq!(e.program, program.to_owned());
140                        assert_eq!(e.code, Some(1 as i32));
141                    },
142                }
143            },
144        };
145    }
146}