command_error/
child_ext.rs

1use std::fmt::Debug;
2use std::fmt::Display;
3use std::process::Child;
4use std::process::ExitStatus;
5use std::process::Output;
6
7use utf8_command::Utf8Output;
8
9use crate::ChildContext;
10#[cfg(doc)]
11use crate::CommandExt;
12
13use crate::Error;
14use crate::ExecError;
15use crate::OutputContext;
16use crate::OutputConversionError;
17use crate::OutputLike;
18use crate::TryWaitContext;
19use crate::WaitError;
20
21/// Checked methods for [`Child`] processes.
22///
23/// This trait is largely the same as [`CommandExt`], with the difference that the
24/// [`ChildExt::output_checked`] methods take `self` as an owned parameter and the
25/// [`CommandExt::output_checked`] methods take `self` as a mutable reference.
26///
27/// Additionally, methods that return an [`ExitStatus`] are named
28/// [`wait_checked`][`ChildExt::wait_checked`] instead of
29/// [`status_checked`][`CommandExt::status_checked`], to match the method names on [`Child`].
30pub trait ChildExt: Sized {
31    /// The error type returned from methods on this trait.
32    type Error: From<Error>;
33
34    /// Wait for the process to complete, capturing its output. `succeeded` is called and returned
35    /// to determine if the command succeeded.
36    ///
37    /// See [`CommandExt::output_checked_as`] for more information.
38    #[track_caller]
39    fn output_checked_as<O, R, E>(
40        self,
41        succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
42    ) -> Result<R, E>
43    where
44        O: Debug,
45        O: OutputLike,
46        O: 'static,
47        O: Send,
48        O: Sync,
49        O: TryFrom<Output>,
50        <O as TryFrom<Output>>::Error: Display + Send + Sync,
51        E: From<Self::Error>;
52
53    /// Wait for the process to complete, capturing its output. `succeeded` is called and used to
54    /// determine if the command succeeded and (optionally) to add an additional message to the error returned.
55    ///
56    /// See [`CommandExt::output_checked_with`] and [`Child::wait_with_output`] for more information.
57    #[track_caller]
58    fn output_checked_with<O, E>(
59        self,
60        succeeded: impl Fn(&O) -> Result<(), Option<E>>,
61    ) -> Result<O, Self::Error>
62    where
63        O: Debug,
64        O: OutputLike,
65        O: TryFrom<Output>,
66        <O as TryFrom<Output>>::Error: Display + Send + Sync,
67        O: Send + Sync + 'static,
68        E: Debug,
69        E: Display,
70        E: Send + Sync + 'static,
71    {
72        self.output_checked_as(|context| match succeeded(context.output()) {
73            Ok(()) => Ok(context.into_output()),
74            Err(user_error) => Err(context.maybe_error_msg(user_error).into()),
75        })
76    }
77
78    /// Wait for the process to complete, capturing its output. If the command exits with a
79    /// non-zero exit code, an error is raised.
80    ///
81    /// See [`CommandExt::output_checked`] and [`Child::wait_with_output`] for more information.
82    #[track_caller]
83    fn output_checked(self) -> Result<Output, Self::Error> {
84        self.output_checked_with(|output: &Output| {
85            if output.status.success() {
86                Ok(())
87            } else {
88                Err(None::<String>)
89            }
90        })
91    }
92
93    /// Wait for the process to exit, capturing its output and decoding it as UTF-8. If the command
94    /// exits with a non-zero exit code, an error is raised.
95    ///
96    /// See [`CommandExt::output_checked_utf8`] and [`Child::wait_with_output`] for more information.
97    #[track_caller]
98    fn output_checked_utf8(self) -> Result<Utf8Output, Self::Error> {
99        self.output_checked_with_utf8(|output| {
100            if output.status.success() {
101                Ok(())
102            } else {
103                Err(None::<String>)
104            }
105        })
106    }
107
108    /// Wait for the process to exit, capturing its output and decoding it as UTF-8. `succeeded` is
109    /// called and used to determine if the command succeeded and (optionally) to add an additional
110    /// message to the error returned.
111    ///
112    /// See [`CommandExt::output_checked_with_utf8`] and [`Child::wait_with_output`] for more information.
113    #[track_caller]
114    fn output_checked_with_utf8<E>(
115        self,
116        succeeded: impl Fn(&Utf8Output) -> Result<(), Option<E>>,
117    ) -> Result<Utf8Output, Self::Error>
118    where
119        E: Display,
120        E: Debug,
121        E: Send + Sync + 'static,
122    {
123        self.output_checked_with(succeeded)
124    }
125
126    /// Check if the process has exited.
127    ///
128    /// The `succeeded` closure is called and returned to determine the result.
129    ///
130    /// Errors while attempting to retrieve the process's exit status are returned as
131    /// [`WaitError`]s.
132    ///
133    /// See [`Child::try_wait`] for more information.
134    #[track_caller]
135    fn try_wait_checked_as<R, E>(
136        &mut self,
137        succeeded: impl Fn(TryWaitContext) -> Result<R, E>,
138    ) -> Result<R, E>
139    where
140        E: From<Self::Error>;
141
142    /// Check if the process has exited and, if it failed, return an error.
143    ///
144    /// Errors while attempting to retrieve the process's exit status are transformed into
145    /// [`WaitError`]s.
146    ///
147    /// See [`Child::try_wait`] for more information.
148    #[track_caller]
149    fn try_wait_checked(&mut self) -> Result<Option<ExitStatus>, Self::Error> {
150        self.try_wait_checked_as(|context| match context.into_output_context() {
151            Some(context) => {
152                if context.status().success() {
153                    Ok(Some(context.status()))
154                } else {
155                    Err(context.error().into())
156                }
157            }
158            None => Ok(None),
159        })
160    }
161
162    /// Wait for the process to exit. `succeeded` is called and returned to determine
163    /// if the command succeeded.
164    ///
165    /// See [`CommandExt::status_checked_as`] and [`Child::wait`] for more information.
166    #[track_caller]
167    fn wait_checked_as<R, E>(
168        &mut self,
169        succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
170    ) -> Result<R, E>
171    where
172        E: From<Self::Error>;
173
174    /// Wait for the process to exit. `succeeded` is called and used to determine
175    /// if the command succeeded and (optionally) to add an additional message to the error
176    /// returned.
177    ///
178    /// See [`CommandExt::status_checked_with`] and [`Child::wait`] for more information.
179    #[track_caller]
180    fn wait_checked_with<E>(
181        &mut self,
182        succeeded: impl Fn(ExitStatus) -> Result<(), Option<E>>,
183    ) -> Result<ExitStatus, Self::Error>
184    where
185        E: Debug,
186        E: Display,
187        E: Send + Sync + 'static,
188    {
189        self.wait_checked_as(|context| match succeeded(context.status()) {
190            Ok(()) => Ok(context.status()),
191            Err(user_error) => Err(context.maybe_error_msg(user_error).into()),
192        })
193    }
194
195    /// Wait for the process to exit. If the command exits with a non-zero status
196    /// code, an error is raised containing information about the command that was run.
197    ///
198    /// See [`CommandExt::status_checked`] and [`Child::wait`] for more information.
199    #[track_caller]
200    fn wait_checked(&mut self) -> Result<ExitStatus, Self::Error> {
201        self.wait_checked_with(|status| {
202            if status.success() {
203                Ok(())
204            } else {
205                Err(None::<String>)
206            }
207        })
208    }
209
210    /// Log the command that will be run.
211    ///
212    /// With the `tracing` feature enabled, this will emit a debug-level log with message
213    /// `Executing command` and a `command` field containing the displayed command (by default,
214    /// shell-quoted).
215    fn log(&self) -> Result<(), Self::Error>;
216}
217
218impl ChildExt for ChildContext<Child> {
219    type Error = Error;
220
221    fn output_checked_as<O, R, E>(
222        self,
223        succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
224    ) -> Result<R, E>
225    where
226        O: Debug,
227        O: OutputLike,
228        O: 'static,
229        O: Send,
230        O: Sync,
231        O: TryFrom<Output>,
232        <O as TryFrom<Output>>::Error: Display + Send + Sync,
233        E: From<Self::Error>,
234    {
235        self.log()?;
236        let (child, command) = self.into_child_and_command();
237        match child.wait_with_output() {
238            Ok(output) => match output.try_into() {
239                Ok(output) => succeeded(OutputContext::new(output, command)),
240                Err(error) => {
241                    Err(Error::from(OutputConversionError::new(command, Box::new(error))).into())
242                }
243            },
244            Err(inner) => Err(Error::from(ExecError::new(command, inner)).into()),
245        }
246    }
247
248    fn try_wait_checked_as<R, E>(
249        &mut self,
250        succeeded: impl Fn(TryWaitContext) -> Result<R, E>,
251    ) -> Result<R, E>
252    where
253        E: From<Self::Error>,
254    {
255        let command = self.command_boxed().clone();
256        match self.child_mut().try_wait() {
257            Ok(status) => succeeded(TryWaitContext::new(status, command)),
258            Err(inner) => Err(Error::from(WaitError::new(command, inner)).into()),
259        }
260    }
261
262    fn wait_checked_as<R, E>(
263        &mut self,
264        succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
265    ) -> Result<R, E>
266    where
267        E: From<Self::Error>,
268    {
269        self.log()?;
270        let command = self.command_boxed().clone();
271        match self.child_mut().wait() {
272            Ok(status) => succeeded(OutputContext::new(status, command)),
273            Err(inner) => Err(Error::from(ExecError::new(command, inner)).into()),
274        }
275    }
276
277    fn log(&self) -> Result<(), Self::Error> {
278        #[cfg(feature = "tracing")]
279        {
280            tracing::debug!(command = %self.command(), "Executing command");
281        }
282        Ok(())
283    }
284}