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