asimov_runner/completion.rs
1// This is free and unencumbered software released into the public domain.
2
3//! Explicit process-exit and input-delivery outcomes.
4
5use crate::{ExecutorError, ExecutorResult};
6use std::{io, io::Cursor, process::Output};
7
8/// What happened while supplying a child's stdin.
9#[derive(Debug)]
10pub enum InputCompletion {
11 /// The source reached EOF and all bytes were written, or input was ignored.
12 /// This confirms delivery to the pipe, not application-level processing.
13 Complete,
14 /// The child exited while its input feed was still pending. The source was
15 /// cancelled, so unread data and upstream errors may remain unobserved.
16 Interrupted,
17 /// Reading the source failed. The child was terminated and reaped.
18 SourceFailed(ExecutorError),
19 /// Writing or closing stdin failed. Broken pipes allow the child to finish
20 /// naturally so its exit diagnostics can be collected; other errors terminate it.
21 WriteFailed(io::Error),
22}
23
24/// A reaped child's output and input-delivery outcome, including unsuccessful exits.
25///
26/// Returned by [`crate::Executor::execute_with_io_completion`]. Inspect both
27/// fields when early child exit is intentional. Capture/forwarding and wait
28/// errors are returned directly instead of producing a completion value.
29#[derive(Debug)]
30pub struct ExecutionCompletion {
31 /// Exit status, captured stderr, and captured stdout. Forwarded stdout is empty.
32 pub output: Output,
33 /// Whether the input source finished and all its bytes reached the stdin pipe.
34 pub input: InputCompletion,
35}
36
37impl ExecutionCompletion {
38 /// Requires a successful exit and complete input delivery, returning stdout.
39 ///
40 /// Error precedence is: source errors and non-broken-pipe stdin errors
41 /// (which cause termination), unsuccessful child exit with stderr, then
42 /// broken-pipe errors or [`ExecutorError::IncompleteInput`]. Thus an exit
43 /// failure is not hidden by a downstream broken pipe, and an upstream error
44 /// is not hidden by the termination it caused. Ready input outcomes are
45 /// polled before child exit; pending sources are never drained after exit.
46 pub fn into_result(self) -> ExecutorResult {
47 match self.input {
48 InputCompletion::SourceFailed(error) => return Err(error),
49 InputCompletion::WriteFailed(error) if error.kind() != io::ErrorKind::BrokenPipe => {
50 return Err(error.into());
51 },
52 input => {
53 if !self.output.status.success() {
54 return Err(self.output.into());
55 }
56 match input {
57 InputCompletion::Complete => {},
58 InputCompletion::Interrupted => return Err(ExecutorError::IncompleteInput),
59 InputCompletion::WriteFailed(error) => return Err(error.into()),
60 InputCompletion::SourceFailed(_) => unreachable!(),
61 }
62 },
63 }
64 Ok(Cursor::new(self.output.stdout))
65 }
66}
67
68pub(crate) enum InputFailure {
69 Source(ExecutorError),
70 Write(io::Error),
71}
72
73impl From<io::Error> for InputFailure {
74 fn from(error: io::Error) -> Self {
75 Self::Write(error)
76 }
77}