Skip to main content

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}