Skip to main content

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}