1use std::io;
2
3#[derive(Debug, thiserror::Error)]
5#[non_exhaustive]
6pub enum ClaudeError {
7 #[error(
9 "claude CLI not found in PATH. Install it from https://docs.anthropic.com/en/docs/claude-code"
10 )]
11 CliNotFound,
12
13 #[error("claude exited with code {code}: {stderr}")]
15 NonZeroExit {
16 code: i32,
18 stderr: String,
20 },
21
22 #[error("failed to parse response")]
24 ParseError(#[from] serde_json::Error),
25
26 #[error("request timed out")]
28 Timeout,
29
30 #[error(transparent)]
32 Io(#[from] io::Error),
33
34 #[error("failed to deserialize structured output: {source}")]
37 StructuredOutputError {
38 raw_result: String,
40 source: serde_json::Error,
42 },
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn cli_not_found_message() {
51 let err = ClaudeError::CliNotFound;
52 assert_eq!(
53 err.to_string(),
54 "claude CLI not found in PATH. Install it from https://docs.anthropic.com/en/docs/claude-code"
55 );
56 }
57
58 #[test]
59 fn non_zero_exit_message() {
60 let err = ClaudeError::NonZeroExit {
61 code: 1,
62 stderr: "something went wrong".into(),
63 };
64 assert_eq!(
65 err.to_string(),
66 "claude exited with code 1: something went wrong"
67 );
68 }
69
70 #[test]
71 fn timeout_message() {
72 let err = ClaudeError::Timeout;
73 assert_eq!(err.to_string(), "request timed out");
74 }
75
76 #[test]
77 fn from_io_error() {
78 let io_err = io::Error::new(io::ErrorKind::Other, "disk full");
79 let err = ClaudeError::from(io_err);
80 assert!(matches!(err, ClaudeError::Io(_)));
81 assert_eq!(err.to_string(), "disk full");
82 }
83
84 #[test]
85 fn from_serde_error() {
86 let serde_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
87 let err = ClaudeError::from(serde_err);
88 assert!(matches!(err, ClaudeError::ParseError(_)));
89 }
90
91 #[test]
92 fn structured_output_error_message() {
93 let serde_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
94 let err = ClaudeError::StructuredOutputError {
95 raw_result: "raw text here".into(),
96 source: serde_err,
97 };
98 assert!(
99 err.to_string()
100 .starts_with("failed to deserialize structured output:")
101 );
102 }
103
104 #[test]
105 fn structured_output_error_preserves_raw_result() {
106 let serde_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
107 let err = ClaudeError::StructuredOutputError {
108 raw_result: "the raw output".into(),
109 source: serde_err,
110 };
111 match err {
112 ClaudeError::StructuredOutputError { raw_result, .. } => {
113 assert_eq!(raw_result, "the raw output");
114 }
115 _ => panic!("expected StructuredOutputError"),
116 }
117 }
118}