1use std::fmt::Debug;
2use std::io::Error as IoError;
3use std::io::Result as IoResult;
4use std::process::Output as StdOutput;
5use std::process::{Child, Command, ExitStatus};
6
7#[cfg(use_std_output)]
8pub type Output = StdOutput;
9
10#[cfg(not(use_std_output))]
11#[derive(Debug, Clone, Eq, PartialEq)]
12pub struct Output {
15 pub stdout: Vec<u8>,
17 pub stderr: Vec<u8>,
19}
20
21#[cfg(not(use_std_output))]
22impl From<StdOutput> for Output {
23 fn from(out: StdOutput) -> Output {
24 Output {
25 stdout: out.stdout,
26 stderr: out.stderr,
27 }
28 }
29}
30
31quick_error! {
32
33 #[derive(Debug)]
37 pub enum Error {
38 Io(err: IoError) {
40 from()
41 description(err.description())
42 cause(err)
43 }
44 Failure(ex: ExitStatus, output: Option<Output>) {
46 description("command failed with nonzero exit code")
47 display("command failed with exit code {}", ex.code()
48 .map(|code|code.to_string())
49 .unwrap_or_else(||"<None> possible terminated by signal".into()))
50 }
51 }
52}
53
54pub trait CommandExt {
57 fn checked_output(&mut self) -> Result<Output, Error>;
68
69 fn checked_status(&mut self) -> Result<(), Error>;
78}
79
80pub trait ChildExt {
83 fn checked_wait_with_output(self) -> Result<Output, Error>;
94
95 fn checked_wait(&mut self) -> Result<(), Error>;
104
105 #[cfg(feature = "process_try_wait")]
142 fn checked_try_wait(&mut self) -> Result<bool, Error>;
143}
144
145impl CommandExt for Command {
146 fn checked_output(&mut self) -> Result<Output, Error> {
147 convert_result(self.output())
148 }
149 fn checked_status(&mut self) -> Result<(), Error> {
150 convert_result(self.status())
151 }
152}
153
154impl ChildExt for Child {
155 fn checked_wait_with_output(self) -> Result<Output, Error> {
156 convert_result(self.wait_with_output())
157 }
158 fn checked_wait(&mut self) -> Result<(), Error> {
159 convert_result(self.wait())
160 }
161
162 #[cfg(feature = "process_try_wait")]
163 fn checked_try_wait(&mut self) -> Result<bool, Error> {
164 convert_result(self.try_wait())
165 }
166}
167
168trait OutputOrExitStatus: Sized {
176 type Out;
177 fn use_ok_result(&self) -> bool;
178 fn create_error(self) -> Error;
179 fn convert(self) -> Self::Out;
180}
181
182#[cfg(feature = "process_try_wait")]
183impl OutputOrExitStatus for Option<ExitStatus> {
184 type Out = bool;
185
186 #[inline]
187 fn use_ok_result(&self) -> bool {
188 self.is_none() || self.unwrap().success()
189 }
190
191 #[inline]
192 fn create_error(self) -> Error {
193 Error::Failure(self.unwrap(), None)
197 }
198
199 #[inline]
200 fn convert(self) -> bool {
201 self.is_some()
202 }
203}
204
205impl OutputOrExitStatus for ExitStatus {
206 type Out = ();
207
208 #[inline]
209 fn use_ok_result(&self) -> bool {
210 self.success()
211 }
212
213 #[inline]
214 fn create_error(self) -> Error {
215 Error::Failure(self, None)
216 }
217
218 #[inline]
219 fn convert(self) -> () {
220 ()
221 }
222}
223
224impl OutputOrExitStatus for StdOutput {
225 type Out = Output;
226
227 #[inline]
228 fn use_ok_result(&self) -> bool {
229 self.status.success()
230 }
231
232 #[inline]
233 fn create_error(self) -> Error {
234 Error::Failure(self.status, Some(self.into()))
238 }
239
240 #[inline]
241 fn convert(self) -> Output {
242 self.into()
243 }
244}
245
246fn convert_result<T>(result: IoResult<T>) -> Result<T::Out, Error>
251where
252 T: OutputOrExitStatus + Debug,
253{
254 match result {
255 Ok(think) => {
256 if think.use_ok_result() {
257 Ok(think.convert())
258 } else {
259 Err(think.create_error())
260 }
261 }
262 Err(io_error) => Err(io_error.into()),
263 }
264}
265
266#[cfg(test)]
267mod tests {
268
269 #[cfg(unix)]
274 mod using_unix_exit_code_ext {
275
276 use super::super::{convert_result, Error, Output};
277 use std::error::Error as StdError;
278 use std::fmt;
279 use std::fmt::Write;
280 use std::io;
281 use std::os::unix::process::ExitStatusExt;
282 use std::process::ExitStatus;
283 use std::process::Output as StdOutput;
284
285 pub fn assert_debugstr_eq<Type: fmt::Debug>(a: Type, b: Type) {
289 let mut buffer_a = String::new();
290 write!(&mut buffer_a, "{:?}", a).expect("debug fmt (a) failed");
291 let mut buffer_b = String::new();
292 write!(&mut buffer_b, "{:?}", b).expect("debug fmt (b) failed");
293
294 assert_eq!(buffer_a, buffer_b);
295 }
296
297 fn ok_exit_status() -> ExitStatus {
298 ExitStatus::from_raw(0)
299 }
300
301 #[cfg(not(target_os = "haiku"))]
302 fn fail_exit_status() -> ExitStatus {
303 ExitStatus::from_raw(2 << 8)
304 }
305
306 #[cfg(target_os = "haiku")]
307 fn fail_exit_status() -> ExitStatus {
308 ExitStatus::from_raw(2)
309 }
310
311 #[cfg(not(target_os = "haiku"))]
312 fn fail_exit_status_none() -> ExitStatus {
313 ExitStatus::from_raw(2)
314 }
315
316 #[cfg(target_os = "haiku")]
317 fn fail_exit_status_none() -> ExitStatus {
318 ExitStatus::from_raw(2 << 8)
319 }
320
321 fn create_output(ex: ExitStatus) -> StdOutput {
322 StdOutput {
323 status: ex,
324 stderr: vec![1, 2, 3],
325 stdout: vec![1, 2, 3],
326 }
327 }
328
329 #[test]
330 fn conv_result_status_ok() {
331 let res = convert_result(Ok(ok_exit_status()));
332 assert_debugstr_eq(Ok(()), res);
333 }
334
335 #[test]
336 fn conv_result_status_fail() {
337 let fail_status = fail_exit_status();
338 let res = convert_result(Ok(fail_status));
339 assert_debugstr_eq(Err(Error::Failure(fail_status, None)), res);
340 }
341
342 #[test]
343 fn conv_result_status_io_error() {
344 let ioerr = io::Error::new(io::ErrorKind::Other, "bla");
345 let ioerr2 = io::Error::new(io::ErrorKind::Other, "bla");
346 let res: Result<(), Error> = convert_result::<ExitStatus>(Err(ioerr));
347 assert_debugstr_eq(Err(Error::Io(ioerr2)), res)
348 }
349
350 #[test]
351 fn conv_result_output_ok() {
352 let out = create_output(ok_exit_status());
353 let out2 = out.clone();
354 assert_debugstr_eq(Ok(out2.into()), convert_result(Ok(out)));
355 }
356
357 #[test]
358 fn conv_result_output_fail() {
359 let fail_status = fail_exit_status();
360 let out = create_output(fail_status);
361 let out2 = out.clone();
362 assert_debugstr_eq(
363 Err(Error::Failure(fail_status, Some(out2.into()))),
364 convert_result(Ok(out)),
365 )
366 }
367
368 #[test]
369 fn conv_result_output_io_error() {
370 let ioerr = io::Error::new(io::ErrorKind::Other, "bla");
371 let ioerr2 = io::Error::new(io::ErrorKind::Other, "bla");
372 let res: Result<Output, Error> = convert_result::<StdOutput>(Err(ioerr));
373 assert_debugstr_eq(Err(Error::Io(ioerr2)), res)
374 }
375
376 #[cfg(feature = "process_try_wait")]
377 #[test]
378 fn conv_result_not_ready() {
379 match convert_result(Ok(None)) {
380 Ok(false) => {}
381 e => panic!("expected `Ok(false)` got `{:?}`", e),
382 }
383 }
384
385 #[cfg(feature = "process_try_wait")]
386 #[test]
387 fn conv_result_ready_ok() {
388 match convert_result(Ok(Some(ok_exit_status()))) {
389 Ok(true) => {}
390 e => panic!("expected `Ok(true)` got `{:?}`", e),
391 }
392 }
393
394 #[cfg(feature = "process_try_wait")]
395 #[test]
396 fn conv_result_ready_failure() {
397 let fail_status = fail_exit_status();
398 let res = convert_result(Ok(Some(fail_status)));
399 assert_debugstr_eq(Err(Error::Failure(fail_status, None)), res);
400 }
401
402 #[ignore = "broken due to deprecation of description changing io::Error::cause"]
403 #[allow(deprecated)]
404 #[test]
405 fn error_io_cause() {
406 let ioerr = || io::Error::new(io::ErrorKind::Other, "bla");
407 let err = Error::Io(ioerr());
408 assert_debugstr_eq(&ioerr() as &dyn StdError, err.cause().unwrap());
409 }
410
411 #[ignore = "broken due to deprecation of description changing io::Error::description"]
412 #[allow(deprecated)]
413 #[test]
414 fn error_io_description() {
415 let ioerr = io::Error::new(io::ErrorKind::Other, "bla");
416 let desc: String = ioerr.description().into();
417 let got: String = Error::Io(ioerr).description().into();
418 assert_eq!(desc, got);
419 }
420
421 #[test]
422 fn error_failure_display() {
423 let err = Error::Failure(fail_exit_status(), None);
424 assert_eq!(format!("{}", err), "command failed with exit code 2");
425 }
426
427 #[test]
428 fn error_failure_no_code_display() {
429 let err = Error::Failure(fail_exit_status_none(), None);
430 assert_eq!(
431 format!("{}", err),
432 "command failed with exit code <None> possible terminated by signal"
433 );
434 }
435
436 #[test]
437 fn from_raw_ok() {
438 let ex1 = ok_exit_status();
439 assert_eq!(true, ex1.success());
440 assert_eq!(Some(0), ex1.code());
441 }
442
443 #[test]
444 fn from_raw_fail() {
445 let ex1 = fail_exit_status();
446 assert_eq!(false, ex1.success());
447 assert_eq!(Some(2), ex1.code());
448 }
449
450 #[test]
451 fn from_raw_fail_none() {
452 let ex1 = fail_exit_status_none();
453 assert_eq!(false, ex1.success());
454 assert_eq!(None, ex1.code());
455 }
456 }
457}