Skip to main content

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}