kawauso_process/error/run.rs
1//! The error of running a command
2
3use thiserror::Error;
4
5use crate::invocation::Invocation;
6
7/// The error returned when a command does not run
8///
9/// The variants separate what happened to the command. A command that never
10/// started needs a program that exists, a working directory that exists, and
11/// the rights to start a program. A run that started and did not finish means
12/// that the operating system stopped the crate from collecting the result,
13/// which a caller reports and can try again.
14///
15/// A command that ran and ended without success is no failure of the run. The
16/// result then carries the status, and the caller decides what it means.
17///
18/// The invocation travels in a box. A result carries its error by value, so
19/// the size of the error reaches every function that returns one. The box
20/// keeps the error small, and a caller that wraps it in an error of its own
21/// needs no box for it.
22///
23/// A later release can add variants, and it can add fields to a variant.
24/// Match with a wildcard arm, and bind the fields of a variant with `..`.
25#[derive(Debug, Error)]
26#[non_exhaustive]
27pub enum RunCommandError {
28 /// The command started and the run did not finish
29 ///
30 /// The program ran, and reading one of its streams or waiting for its end
31 /// failed. Nothing of the run survives such a failure, so the crate
32 /// reports it instead of a result with holes in it. The command itself
33 /// can still have done its work.
34 // process[impl run.error.message]
35 #[error("failed to complete the run of the command `{invocation}`")]
36 #[non_exhaustive]
37 IncompleteRun {
38 /// The command that did not finish
39 invocation: Box<Invocation>,
40
41 /// The cause of the failure
42 source: std::io::Error,
43 },
44
45 /// The command did not start
46 ///
47 /// No program answers to the name, the working directory does not exist,
48 /// or the operating system refused to start the program. Nothing ran, so
49 /// there is no status and no output to report.
50 // process[impl run.error]
51 // process[impl run.error.message]
52 #[error("failed to start the command `{invocation}`")]
53 #[non_exhaustive]
54 UnstartableCommand {
55 /// The command that did not start
56 invocation: Box<Invocation>,
57
58 /// The cause of the failure
59 source: std::io::Error,
60 },
61}
62
63#[cfg(test)]
64mod tests {
65 // An assertion in a test panics by design. A `# Panics` section on every
66 // test would repeat that and give the reader no information.
67 #![allow(clippy::missing_panics_doc)]
68
69 use super::*;
70
71 // A caller sends the error between threads and keeps it in a report that
72 // another thread reads. This test holds the error to the auto traits that
73 // make this possible, because a private field of a later version could
74 // take them away without a word from the compiler.
75 #[test]
76 fn run_command_error_is_send_and_sync() {
77 fn assert_send_and_sync<T: Send + Sync>() {}
78
79 assert_send_and_sync::<RunCommandError>();
80 }
81
82 // A result carries its error by value, and a consumer wraps the error in
83 // an error of its own. An error that grows makes every such wrapper grow
84 // with it, until the wrapper needs a box that the consumer writes. This
85 // test holds the error to a size that leaves the consumer that room.
86 #[test]
87 fn run_command_error_stays_small() {
88 assert!(size_of::<RunCommandError>() <= 4 * size_of::<usize>());
89 }
90}