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 time::Duration,
12};
13
14use thiserror::Error;
15
16use crate::{
17 CommandOutput,
18 OutputStream,
19};
20
21/// Error returned while spawning, waiting for, or validating a command.
22///
23/// # Author
24///
25/// Haixing Hu
26#[derive(Debug, Error)]
27pub enum CommandError {
28 /// The process could not be spawned.
29 #[error("failed to spawn command `{command}`: {source}")]
30 SpawnFailed {
31 /// Human-readable command representation.
32 command: String,
33 /// I/O error reported by the operating system.
34 source: io::Error,
35 },
36
37 /// Waiting for process completion failed.
38 #[error("failed to wait for command `{command}`: {source}")]
39 WaitFailed {
40 /// Human-readable command representation.
41 command: String,
42 /// I/O error reported while waiting for the child process.
43 source: io::Error,
44 },
45
46 /// The process could not be killed after exceeding the configured timeout.
47 #[error("failed to kill timed-out command `{command}` after {timeout:?}: {source}")]
48 KillFailed {
49 /// Human-readable command representation.
50 command: String,
51 /// Timeout that was exceeded.
52 timeout: Duration,
53 /// I/O error reported while killing the child process.
54 source: io::Error,
55 },
56
57 /// Reading one of the captured output streams failed.
58 #[error("failed to read {stream} for command `{command}`: {source}")]
59 ReadOutputFailed {
60 /// Human-readable command representation.
61 command: String,
62 /// Stream whose reader failed.
63 stream: OutputStream,
64 /// I/O error reported while reading the stream.
65 source: io::Error,
66 },
67
68 /// The command exceeded the configured timeout and was terminated.
69 #[error("command `{command}` timed out after {timeout:?}")]
70 TimedOut {
71 /// Human-readable command representation.
72 command: String,
73 /// Timeout that was exceeded.
74 timeout: Duration,
75 /// Captured output available after terminating the child process.
76 output: Box<CommandOutput>,
77 },
78
79 /// The command completed with an exit code not configured as successful.
80 #[error("command `{command}` exited with code {exit_code:?}; expected one of {expected:?}")]
81 UnexpectedExit {
82 /// Human-readable command representation.
83 command: String,
84 /// Exit code reported by the process, if available.
85 exit_code: Option<i32>,
86 /// Configured successful exit codes.
87 expected: Vec<i32>,
88 /// Captured output from the failed command.
89 output: Box<CommandOutput>,
90 },
91}
92
93impl CommandError {
94 /// Returns captured command output when this error carries it.
95 ///
96 /// # Returns
97 ///
98 /// `Some(output)` for timeout and unexpected-exit errors, otherwise `None`.
99 #[inline]
100 pub const fn output(&self) -> Option<&CommandOutput> {
101 match self {
102 Self::TimedOut { output, .. } | Self::UnexpectedExit { output, .. } => Some(output),
103 _ => None,
104 }
105 }
106
107 /// Returns the command string associated with this error.
108 ///
109 /// # Returns
110 ///
111 /// A human-readable command representation used in diagnostics.
112 #[inline]
113 pub fn command(&self) -> &str {
114 match self {
115 Self::SpawnFailed { command, .. }
116 | Self::WaitFailed { command, .. }
117 | Self::KillFailed { command, .. }
118 | Self::ReadOutputFailed { command, .. }
119 | Self::TimedOut { command, .. }
120 | Self::UnexpectedExit { command, .. } => command,
121 }
122 }
123}