qubit_command/command_error.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9use std::{
10 io,
11 path::PathBuf,
12 time::Duration,
13};
14
15use thiserror::Error;
16
17use crate::{
18 CommandOutput,
19 OutputStream,
20};
21
22/// Error returned while spawning, waiting for, or validating a command.
23///
24/// # Author
25///
26/// Haixing Hu
27#[derive(Debug, Error)]
28pub enum CommandError {
29 /// The process could not be spawned.
30 #[error("failed to spawn command `{command}`: {source}")]
31 SpawnFailed {
32 /// Human-readable command representation.
33 command: String,
34 /// I/O error reported by the operating system.
35 source: io::Error,
36 },
37
38 /// Waiting for process completion failed.
39 #[error("failed to wait for command `{command}`: {source}")]
40 WaitFailed {
41 /// Human-readable command representation.
42 command: String,
43 /// I/O error reported while waiting for the child process.
44 source: io::Error,
45 },
46
47 /// The process could not be killed after exceeding the configured timeout.
48 #[error("failed to kill timed-out command `{command}` after {timeout:?}: {source}")]
49 KillFailed {
50 /// Human-readable command representation.
51 command: String,
52 /// Timeout that was exceeded.
53 timeout: Duration,
54 /// I/O error reported while killing the child process.
55 source: io::Error,
56 },
57
58 /// Reading one of the captured output streams failed.
59 #[error("failed to read {stream} for command `{command}`: {source}")]
60 ReadOutputFailed {
61 /// Human-readable command representation.
62 command: String,
63 /// Stream whose reader failed.
64 stream: OutputStream,
65 /// I/O error reported while reading the stream.
66 source: io::Error,
67 },
68
69 /// Opening a stdin file failed.
70 #[error("failed to open stdin file `{path:?}` for command `{command}`: {source}")]
71 OpenInputFailed {
72 /// Human-readable command representation.
73 command: String,
74 /// Path that could not be opened.
75 path: PathBuf,
76 /// I/O error reported while opening the file.
77 source: io::Error,
78 },
79
80 /// Opening an output redirection file failed.
81 #[error("failed to open {stream} file `{path:?}` for command `{command}`: {source}")]
82 OpenOutputFailed {
83 /// Human-readable command representation.
84 command: String,
85 /// Stream whose file could not be opened.
86 stream: OutputStream,
87 /// Path that could not be opened.
88 path: PathBuf,
89 /// I/O error reported while opening the file.
90 source: io::Error,
91 },
92
93 /// Writing configured stdin bytes failed.
94 #[error("failed to write stdin for command `{command}`: {source}")]
95 WriteInputFailed {
96 /// Human-readable command representation.
97 command: String,
98 /// I/O error reported while writing stdin.
99 source: io::Error,
100 },
101
102 /// Writing captured output to a redirection file failed.
103 #[error("failed to write {stream} for command `{command}` to `{path:?}`: {source}")]
104 WriteOutputFailed {
105 /// Human-readable command representation.
106 command: String,
107 /// Stream whose redirected writer failed.
108 stream: OutputStream,
109 /// Path that could not be written.
110 path: PathBuf,
111 /// I/O error reported while writing the file.
112 source: io::Error,
113 },
114
115 /// The command exceeded the configured timeout and was terminated.
116 #[error("command `{command}` timed out after {timeout:?}")]
117 TimedOut {
118 /// Human-readable command representation.
119 command: String,
120 /// Timeout that was exceeded.
121 timeout: Duration,
122 /// Captured output available after terminating the child process.
123 output: Box<CommandOutput>,
124 },
125
126 /// The command completed with an exit code not configured as successful.
127 #[error("command `{command}` exited with code {exit_code:?}; expected one of {expected:?}")]
128 UnexpectedExit {
129 /// Human-readable command representation.
130 command: String,
131 /// Exit code reported by the process, if available.
132 exit_code: Option<i32>,
133 /// Configured successful exit codes.
134 expected: Vec<i32>,
135 /// Captured output from the failed command.
136 output: Box<CommandOutput>,
137 },
138}
139
140impl CommandError {
141 /// Returns captured command output when this error carries it.
142 ///
143 /// # Returns
144 ///
145 /// `Some(output)` for timeout and unexpected-exit errors, otherwise `None`.
146 #[inline]
147 pub const fn output(&self) -> Option<&CommandOutput> {
148 match self {
149 Self::TimedOut { output, .. } | Self::UnexpectedExit { output, .. } => Some(output),
150 _ => None,
151 }
152 }
153
154 /// Returns the command string associated with this error.
155 ///
156 /// # Returns
157 ///
158 /// A human-readable command representation used in diagnostics.
159 #[inline]
160 pub fn command(&self) -> &str {
161 match self {
162 Self::SpawnFailed { command, .. }
163 | Self::WaitFailed { command, .. }
164 | Self::KillFailed { command, .. }
165 | Self::ReadOutputFailed { command, .. }
166 | Self::OpenInputFailed { command, .. }
167 | Self::OpenOutputFailed { command, .. }
168 | Self::WriteInputFailed { command, .. }
169 | Self::WriteOutputFailed { command, .. }
170 | Self::TimedOut { command, .. }
171 | Self::UnexpectedExit { command, .. } => command,
172 }
173 }
174}