Skip to main content

codex_wrapper/
error.rs

1//! Error types for `codex-wrapper`.
2
3use std::path::PathBuf;
4
5/// Errors returned by `codex-wrapper` operations.
6#[derive(Debug, thiserror::Error)]
7pub enum Error {
8    /// The `codex` binary was not found in PATH.
9    #[error("codex binary not found in PATH")]
10    NotFound,
11
12    /// A codex command failed with a non-zero exit code.
13    #[error("codex command failed: {command} (exit code {exit_code}){}{}{}", working_dir.as_ref().map(|d| format!(" (in {})", d.display())).unwrap_or_default(), if stdout.is_empty() { String::new() } else { format!("\nstdout: {stdout}") }, if stderr.is_empty() { String::new() } else { format!("\nstderr: {stderr}") })]
14    CommandFailed {
15        command: String,
16        exit_code: i32,
17        stdout: String,
18        stderr: String,
19        working_dir: Option<PathBuf>,
20    },
21
22    /// An I/O error occurred while spawning or communicating with the process.
23    #[error("io error: {message}{}", working_dir.as_ref().map(|d| format!(" (in {})", d.display())).unwrap_or_default())]
24    Io {
25        message: String,
26        #[source]
27        source: std::io::Error,
28        working_dir: Option<PathBuf>,
29    },
30
31    /// The command timed out.
32    #[error("codex command timed out after {timeout_seconds}s")]
33    Timeout { timeout_seconds: u64 },
34
35    /// JSON parsing failed.
36    #[cfg(feature = "json")]
37    #[error("json parse error: {message}")]
38    Json {
39        message: String,
40        #[source]
41        source: serde_json::Error,
42    },
43
44    /// The installed CLI version does not meet the minimum requirement.
45    #[error("CLI version {found} does not meet minimum requirement {minimum}")]
46    VersionMismatch {
47        found: crate::version::CliVersion,
48        minimum: crate::version::CliVersion,
49    },
50}
51
52impl From<std::io::Error> for Error {
53    fn from(e: std::io::Error) -> Self {
54        Self::Io {
55            message: e.to_string(),
56            source: e,
57            working_dir: None,
58        }
59    }
60}
61
62/// Result type alias for codex-wrapper operations.
63pub type Result<T> = std::result::Result<T, Error>;
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn display_not_found() {
71        let err = Error::NotFound;
72        assert_eq!(err.to_string(), "codex binary not found in PATH");
73    }
74
75    #[test]
76    fn display_command_failed_minimal() {
77        let err = Error::CommandFailed {
78            command: "exec".to_string(),
79            exit_code: 1,
80            stdout: String::new(),
81            stderr: String::new(),
82            working_dir: None,
83        };
84        assert_eq!(err.to_string(), "codex command failed: exec (exit code 1)");
85    }
86
87    #[test]
88    fn display_command_failed_with_all_fields() {
89        let err = Error::CommandFailed {
90            command: "exec".to_string(),
91            exit_code: 2,
92            stdout: "out".to_string(),
93            stderr: "err".to_string(),
94            working_dir: Some(PathBuf::from("/tmp")),
95        };
96        assert_eq!(
97            err.to_string(),
98            "codex command failed: exec (exit code 2) (in /tmp)\nstdout: out\nstderr: err"
99        );
100    }
101
102    #[test]
103    fn display_io_without_working_dir() {
104        let source = std::io::Error::other("disk full");
105        let err = Error::Io {
106            message: source.to_string(),
107            source,
108            working_dir: None,
109        };
110        assert_eq!(err.to_string(), "io error: disk full");
111    }
112
113    #[test]
114    fn display_io_with_working_dir() {
115        let source = std::io::Error::other("disk full");
116        let err = Error::Io {
117            message: source.to_string(),
118            source,
119            working_dir: Some(PathBuf::from("/home/user")),
120        };
121        assert_eq!(err.to_string(), "io error: disk full (in /home/user)");
122    }
123
124    #[test]
125    fn display_timeout() {
126        let err = Error::Timeout {
127            timeout_seconds: 30,
128        };
129        assert_eq!(err.to_string(), "codex command timed out after 30s");
130    }
131
132    #[cfg(feature = "json")]
133    #[test]
134    fn display_json() {
135        let source: serde_json::Error =
136            serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
137        let err = Error::Json {
138            message: source.to_string(),
139            source,
140        };
141        assert!(err.to_string().starts_with("json parse error:"));
142    }
143
144    #[test]
145    fn display_version_mismatch() {
146        let err = Error::VersionMismatch {
147            found: crate::version::CliVersion::new(0, 100, 0),
148            minimum: crate::version::CliVersion::new(0, 116, 0),
149        };
150        assert_eq!(
151            err.to_string(),
152            "CLI version 0.100.0 does not meet minimum requirement 0.116.0"
153        );
154    }
155}