clawless_core/process/error.rs
1//! The error of running an external program from a command
2
3use kawauso_process::Invocation;
4use kawauso_process::error::RunCommandError;
5use thiserror::Error;
6
7use crate::event::SendError;
8
9/// The error returned when a command cannot run an external program
10///
11/// The variants separate what a caller does about the failure. A command that
12/// was cancelled stops, a command whose program never ran reports the program,
13/// and a command whose output can no longer be reported has lost its presenter
14/// and ends.
15///
16/// A program that ran and ended without success is no failure of the run. The
17/// [`Execution`] then carries the status, and [`require_success`][success] turns
18/// a status that the caller cannot accept into an error of its own.
19///
20/// A later release can add variants, and it can add fields to a variant. Match
21/// with a wildcard arm, and bind the fields of a variant with `..`.
22///
23/// [`Execution`]: kawauso_process::Execution
24/// [success]: kawauso_process::Execution::require_success
25// r[impl process.error]
26#[derive(Debug, Error)]
27#[non_exhaustive]
28pub enum RunProcessError {
29 /// Cancellation stopped the program before it ended
30 ///
31 /// Clawless killed the program, so the run has no exit status and no
32 /// complete output. A command that treats cancellation as an orderly end
33 /// matches this variant and returns without an error.
34 // r[impl process.run.cancel.error]
35 #[error("the command `{invocation}` was cancelled before it ended")]
36 #[non_exhaustive]
37 CancelledRun {
38 /// The command that was cancelled
39 invocation: Invocation,
40 },
41
42 /// The program could not be run
43 ///
44 /// No program answers to the name, the working directory does not exist,
45 /// the operating system refused to start the program, or the run started
46 /// and its result could not be collected. The message is the one of the
47 /// underlying failure, because this layer knows nothing that the run does
48 /// not already state.
49 // r[impl process.run.error]
50 #[error(transparent)]
51 #[non_exhaustive]
52 UnrunnableCommand {
53 /// The cause of the failure
54 source: RunCommandError,
55 },
56
57 /// The output of the program could not be reported
58 ///
59 /// The presenter stopped listening while the program ran, so the run has no
60 /// consumer left. The program itself was killed with the run.
61 // r[impl process.run.report.error]
62 #[error("failed to report the output of the command `{invocation}`")]
63 #[non_exhaustive]
64 UnreportableOutput {
65 /// The command whose output could not be reported
66 invocation: Invocation,
67
68 /// The cause of the failure
69 source: SendError,
70 },
71}
72
73#[cfg(test)]
74mod tests {
75 // An assertion in a test panics by design. A `# Panics` section on every test
76 // would repeat that and give the reader no information.
77 #![allow(clippy::missing_panics_doc)]
78
79 use super::*;
80
81 // r[verify process.error]
82 #[test]
83 fn display_with_a_cancelled_run_names_the_command() {
84 let error = RunProcessError::CancelledRun {
85 invocation: Invocation::new("git").arg("status"),
86 };
87
88 let message = error.to_string();
89
90 assert_eq!(
91 message,
92 "the command `git status` was cancelled before it ended"
93 );
94 }
95
96 #[test]
97 fn display_with_unreportable_output_names_the_command() {
98 let error = RunProcessError::UnreportableOutput {
99 invocation: Invocation::new("git").arg("status"),
100 source: SendError(crate::event::Event::Message("lost".to_owned())),
101 };
102
103 let message = error.to_string();
104
105 assert_eq!(
106 message,
107 "failed to report the output of the command `git status`"
108 );
109 }
110
111 #[test]
112 fn trait_send() {
113 fn assert_send<T: Send>() {}
114 assert_send::<RunProcessError>();
115 }
116
117 #[test]
118 fn trait_sync() {
119 fn assert_sync<T: Sync>() {}
120 assert_sync::<RunProcessError>();
121 }
122
123 #[test]
124 fn trait_unpin() {
125 fn assert_unpin<T: Unpin>() {}
126 assert_unpin::<RunProcessError>();
127 }
128}