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
21pub trait ChildExt: Sized {
31 type Error: From<Error>;
33
34 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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}