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/// A later release can add variants, and it can add fields to a variant.
23/// Match with a wildcard arm, and bind the fields of a variant with `..`.
24#[derive(Clone, Eq, PartialEq, Debug)]
25#[non_exhaustive]
26pub enum RequireSuccessError {
27 /// The command ended without success
28 ///
29 /// The exit status says how the command ended. A command that ran to its
30 /// end reports a code, and a command that a signal stopped reports no
31 /// code at all.
32 #[non_exhaustive]
33 UnsuccessfulCommand {
34 /// The command that ended without success
35 invocation: Invocation,
36
37 /// The status with which the command ended
38 status: ExitStatus,
39
40 /// What the command wrote to its standard error
41 stderr: Capture,
42 },
43}
44
45/// States which command ended how, and what it wrote about it
46///
47/// The message holds the capture of the standard error, because the reader of
48/// a report has only this message and the command wrote its reason there. A
49/// command that wrote nothing says so, so that no reader looks for a text
50/// that does not exist.
51// process[impl run.success.message]
52impl Display for RequireSuccessError {
53 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
54 match self {
55 Self::UnsuccessfulCommand {
56 invocation,
57 status,
58 stderr,
59 } => {
60 write!(formatter, "the command `{invocation}` ended with {status}")?;
61
62 let text = stderr.to_string_lossy();
63 let reason = text.trim();
64
65 if reason.is_empty() {
66 formatter.write_str(", and it wrote nothing to its standard error")
67 } else {
68 write!(formatter, ", and it wrote `{reason}` to its standard error")
69 }
70 }
71 }
72 }
73}
74
75/// Reports the error to the tools of the ecosystem
76///
77/// The error has no source. The command ran, and no other operation failed
78/// under this one.
79impl Error for RequireSuccessError {}
80
81#[cfg(test)]
82mod tests {
83 // An assertion in a test panics by design. A `# Panics` section on every
84 // test would repeat that and give the reader no information.
85 #![allow(clippy::missing_panics_doc)]
86
87 use super::*;
88
89 // A caller sends the error between threads and keeps it in a report that
90 // another thread reads. This test holds the error to the auto traits that
91 // make this possible, because a private field of a later version could
92 // take them away without a word from the compiler.
93 #[test]
94 fn require_success_error_is_send_and_sync() {
95 fn assert_send_and_sync<T: Send + Sync>() {}
96
97 assert_send_and_sync::<RequireSuccessError>();
98 }
99}