1use std::borrow::Borrow;
2use std::fmt::Debug;
3use std::fmt::Display;
4use std::process::Child;
5use std::process::ExitStatus;
6use std::process::Output;
7
8use utf8_command::Utf8Output;
9
10use crate::ChildContext;
11#[cfg(doc)]
12use crate::CommandExt;
13
14use crate::Error;
15use crate::ExecError;
16use crate::OutputContext;
17use crate::OutputConversionError;
18use crate::OutputLike;
19use crate::TryWaitContext;
20use crate::WaitError;
21
22pub trait ChildExt: Sized {
32 type Error: From<Error>;
34
35 #[track_caller]
40 fn output_checked_as<O, R, E>(
41 self,
42 succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
43 ) -> Result<R, E>
44 where
45 O: Debug,
46 O: OutputLike,
47 O: 'static,
48 O: TryFrom<Output>,
49 <O as TryFrom<Output>>::Error: Display + Send + Sync,
50 E: From<Self::Error>;
51
52 #[track_caller]
57 fn output_checked_with<O, E>(
58 self,
59 succeeded: impl Fn(&O) -> Result<(), Option<E>>,
60 ) -> Result<O, Self::Error>
61 where
62 O: Debug,
63 O: OutputLike,
64 O: TryFrom<Output>,
65 <O as TryFrom<Output>>::Error: Display + Send + Sync,
66 O: Send + Sync + 'static,
67 E: Debug,
68 E: Display,
69 E: Send + Sync + 'static,
70 {
71 self.output_checked_as(|context| match succeeded(context.output()) {
72 Ok(()) => Ok(context.into_output()),
73 Err(user_error) => Err(context.maybe_error_msg(user_error).into()),
74 })
75 }
76
77 #[track_caller]
82 fn output_checked(self) -> Result<Output, Self::Error> {
83 self.output_checked_with(|output: &Output| {
84 if output.status.success() {
85 Ok(())
86 } else {
87 Err(None::<String>)
88 }
89 })
90 }
91
92 #[track_caller]
97 fn output_checked_utf8(self) -> Result<Utf8Output, Self::Error> {
98 self.output_checked_with_utf8(|output| {
99 if output.status.success() {
100 Ok(())
101 } else {
102 Err(None::<String>)
103 }
104 })
105 }
106
107 #[track_caller]
113 fn output_checked_with_utf8<E>(
114 self,
115 succeeded: impl Fn(&Utf8Output) -> Result<(), Option<E>>,
116 ) -> Result<Utf8Output, Self::Error>
117 where
118 E: Display,
119 E: Debug,
120 E: Send + Sync + 'static,
121 {
122 self.output_checked_with(succeeded)
123 }
124
125 #[track_caller]
134 fn try_wait_checked_as<R, E>(
135 &mut self,
136 succeeded: impl Fn(TryWaitContext) -> Result<R, E>,
137 ) -> Result<R, E>
138 where
139 E: From<Self::Error>;
140
141 #[track_caller]
148 fn try_wait_checked(&mut self) -> Result<Option<ExitStatus>, Self::Error> {
149 self.try_wait_checked_as(|context| match context.into_output_context() {
150 Some(context) => {
151 if context.status().success() {
152 Ok(Some(context.status()))
153 } else {
154 Err(context.error().into())
155 }
156 }
157 None => Ok(None),
158 })
159 }
160
161 #[track_caller]
166 fn wait_checked_as<R, E>(
167 &mut self,
168 succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
169 ) -> Result<R, E>
170 where
171 E: From<Self::Error>;
172
173 #[track_caller]
179 fn wait_checked_with<E>(
180 &mut self,
181 succeeded: impl Fn(ExitStatus) -> Result<(), Option<E>>,
182 ) -> Result<ExitStatus, Self::Error>
183 where
184 E: Debug,
185 E: Display,
186 E: Send + Sync + 'static,
187 {
188 self.wait_checked_as(|context| match succeeded(context.status()) {
189 Ok(()) => Ok(context.status()),
190 Err(user_error) => Err(context.maybe_error_msg(user_error).into()),
191 })
192 }
193
194 #[track_caller]
199 fn wait_checked(&mut self) -> Result<ExitStatus, Self::Error> {
200 self.wait_checked_with(|status| {
201 if status.success() {
202 Ok(())
203 } else {
204 Err(None::<String>)
205 }
206 })
207 }
208
209 fn log(&self) -> Result<(), Self::Error>;
215}
216
217impl ChildExt for ChildContext<Child> {
218 type Error = Error;
219
220 fn output_checked_as<O, R, E>(
221 self,
222 succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
223 ) -> Result<R, E>
224 where
225 O: Debug,
226 O: OutputLike,
227 O: 'static,
228 O: TryFrom<Output>,
229 <O as TryFrom<Output>>::Error: Display + Send + Sync,
230 E: From<Self::Error>,
231 {
232 self.log()?;
233 let command = dyn_clone::clone_box(self.command.borrow());
234 match self.child.wait_with_output() {
235 Ok(output) => match output.try_into() {
236 Ok(output) => succeeded(OutputContext { output, command }),
237 Err(error) => Err(Error::from(OutputConversionError {
238 command,
239 inner: Box::new(error),
240 })
241 .into()),
242 },
243 Err(inner) => Err(Error::from(ExecError { command, inner }).into()),
244 }
245 }
246
247 fn try_wait_checked_as<R, E>(
248 &mut self,
249 succeeded: impl Fn(TryWaitContext) -> Result<R, E>,
250 ) -> Result<R, E>
251 where
252 E: From<Self::Error>,
253 {
254 let command = dyn_clone::clone_box(self.command.borrow());
255 match self.child.try_wait() {
256 Ok(status) => succeeded(TryWaitContext { status, command }),
257 Err(inner) => Err(Error::from(WaitError { inner, command }).into()),
258 }
259 }
260
261 fn wait_checked_as<R, E>(
262 &mut self,
263 succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
264 ) -> Result<R, E>
265 where
266 E: From<Self::Error>,
267 {
268 self.log()?;
269 let command = dyn_clone::clone_box(self.command.borrow());
270 match self.child.wait() {
271 Ok(status) => succeeded(OutputContext {
272 output: status,
273 command,
274 }),
275 Err(inner) => Err(Error::from(ExecError { command, inner }).into()),
276 }
277 }
278
279 fn log(&self) -> Result<(), Self::Error> {
280 #[cfg(feature = "tracing")]
281 {
282 tracing::debug!(command = %self.command, "Executing command");
283 }
284 Ok(())
285 }
286}