Skip to main content

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}