use std::io;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ClaudeError {
#[error(
"claude CLI not found in PATH. Install it from https://docs.anthropic.com/en/docs/claude-code"
)]
CliNotFound,
#[error("claude exited with code {code}: {stderr}")]
NonZeroExit {
code: i32,
stderr: String,
},
#[error("failed to parse response")]
ParseError(#[from] serde_json::Error),
#[error("request timed out")]
Timeout,
#[error(transparent)]
Io(#[from] io::Error),
#[error("failed to deserialize structured output: {source}")]
StructuredOutputError {
raw_result: String,
source: serde_json::Error,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_not_found_message() {
let err = ClaudeError::CliNotFound;
assert_eq!(
err.to_string(),
"claude CLI not found in PATH. Install it from https://docs.anthropic.com/en/docs/claude-code"
);
}
#[test]
fn non_zero_exit_message() {
let err = ClaudeError::NonZeroExit {
code: 1,
stderr: "something went wrong".into(),
};
assert_eq!(
err.to_string(),
"claude exited with code 1: something went wrong"
);
}
#[test]
fn timeout_message() {
let err = ClaudeError::Timeout;
assert_eq!(err.to_string(), "request timed out");
}
#[test]
fn from_io_error() {
let io_err = io::Error::new(io::ErrorKind::Other, "disk full");
let err = ClaudeError::from(io_err);
assert!(matches!(err, ClaudeError::Io(_)));
assert_eq!(err.to_string(), "disk full");
}
#[test]
fn from_serde_error() {
let serde_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
let err = ClaudeError::from(serde_err);
assert!(matches!(err, ClaudeError::ParseError(_)));
}
#[test]
fn structured_output_error_message() {
let serde_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
let err = ClaudeError::StructuredOutputError {
raw_result: "raw text here".into(),
source: serde_err,
};
assert!(
err.to_string()
.starts_with("failed to deserialize structured output:")
);
}
#[test]
fn structured_output_error_preserves_raw_result() {
let serde_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
let err = ClaudeError::StructuredOutputError {
raw_result: "the raw output".into(),
source: serde_err,
};
match err {
ClaudeError::StructuredOutputError { raw_result, .. } => {
assert_eq!(raw_result, "the raw output");
}
_ => panic!("expected StructuredOutputError"),
}
}
}