Skip to main content

kawauso_process/error/
require_success.rs

1//! The error of requiring the success of a command
2
3use std::error::Error;
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::fmt::Result;
7use std::process::ExitStatus;
8
9use crate::execution::Capture;
10use crate::invocation::Invocation;
11
12/// The error returned when a command ended without success
13///
14/// The error carries the three answers that the reader of a report needs:
15/// which command ran, how it ended, and what it said. A program states why it
16/// stopped on its standard error, so the error carries that capture and its
17/// message shows it.
18///
19/// The command ran. Nothing failed inside the crate, and the error exists
20/// because the caller stated that only success is acceptable.
21///
22/// The invocation travels in a box. A result carries its error by value, so
23/// the size of the error reaches every function that returns one. The box
24/// keeps the error small, and a caller that wraps it in an error of its own
25/// needs no box for it.
26///
27/// A later release can add variants, and it can add fields to a variant.
28/// Match with a wildcard arm, and bind the fields of a variant with `..`.
29#[derive(Clone, Eq, PartialEq, Debug)]
30#[non_exhaustive]
31pub enum RequireSuccessError {
32    /// The command ended without success
33    ///
34    /// The exit status says how the command ended. A command that ran to its
35    /// end reports a code, and a command that a signal stopped reports no
36    /// code at all.
37    #[non_exhaustive]
38    UnsuccessfulCommand {
39        /// The command that ended without success
40        invocation: Box<Invocation>,
41
42        /// The status with which the command ended
43        status: ExitStatus,
44
45        /// What the command wrote to its standard error
46        stderr: Capture,
47    },
48}
49
50/// States which command ended how, and what it wrote about it
51///
52/// The message holds the capture of the standard error, because the reader of
53/// a report has only this message and the command wrote its reason there. A
54/// command that wrote nothing says so, so that no reader looks for a text
55/// that does not exist.
56// process[impl run.success.message]
57impl Display for RequireSuccessError {
58    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
59        match self {
60            Self::UnsuccessfulCommand {
61                invocation,
62                status,
63                stderr,
64            } => {
65                write!(formatter, "the command `{invocation}` ended with {status}")?;
66
67                let text = stderr.to_string_lossy();
68                let reason = text.trim();
69
70                if reason.is_empty() {
71                    formatter.write_str(", and it wrote nothing to its standard error")
72                } else {
73                    write!(formatter, ", and it wrote `{reason}` to its standard error")
74                }
75            }
76        }
77    }
78}
79
80/// Reports the error to the tools of the ecosystem
81///
82/// The error has no source. The command ran, and no other operation failed
83/// under this one.
84impl Error for RequireSuccessError {}
85
86#[cfg(test)]
87mod tests {
88    // An assertion in a test panics by design. A `# Panics` section on every
89    // test would repeat that and give the reader no information.
90    #![allow(clippy::missing_panics_doc)]
91
92    use super::*;
93
94    // A caller sends the error between threads and keeps it in a report that
95    // another thread reads. This test holds the error to the auto traits that
96    // make this possible, because a private field of a later version could
97    // take them away without a word from the compiler.
98    #[test]
99    fn require_success_error_is_send_and_sync() {
100        fn assert_send_and_sync<T: Send + Sync>() {}
101
102        assert_send_and_sync::<RequireSuccessError>();
103    }
104
105    // A result carries its error by value, and a consumer wraps the error in
106    // an error of its own. An error that grows makes every such wrapper grow
107    // with it, until the wrapper needs a box that the consumer writes. This
108    // test holds the error to a size that leaves the consumer that room.
109    #[test]
110    fn require_success_error_stays_small() {
111        assert!(size_of::<RequireSuccessError>() <= 6 * size_of::<usize>());
112    }
113}