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    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}