Skip to main content

claude_codes/io/
errors.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4
5/// Error type for parsing failures that preserves the raw JSON
6#[derive(Debug, Clone)]
7pub struct ParseError {
8    /// The raw JSON value that failed to parse
9    pub raw_json: Value,
10    /// The underlying serde error message
11    pub error_message: String,
12}
13
14impl fmt::Display for ParseError {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        write!(f, "Failed to parse ClaudeOutput: {}", self.error_message)
17    }
18}
19
20impl std::error::Error for ParseError {}
21
22/// Known Anthropic API error types.
23///
24/// Maps to the `type` field inside an error response from the Anthropic API.
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub enum ApiErrorType {
27    /// Internal server error (HTTP 500)
28    ApiError,
29    /// Service overloaded (HTTP 529)
30    OverloadedError,
31    /// Bad request (HTTP 400)
32    InvalidRequestError,
33    /// Invalid API key (HTTP 401)
34    AuthenticationError,
35    /// Too many requests (HTTP 429)
36    RateLimitError,
37    /// An error type not yet known to this version of the crate.
38    Unknown(String),
39}
40
41impl ApiErrorType {
42    pub fn as_str(&self) -> &str {
43        match self {
44            Self::ApiError => "api_error",
45            Self::OverloadedError => "overloaded_error",
46            Self::InvalidRequestError => "invalid_request_error",
47            Self::AuthenticationError => "authentication_error",
48            Self::RateLimitError => "rate_limit_error",
49            Self::Unknown(s) => s.as_str(),
50        }
51    }
52}
53
54impl fmt::Display for ApiErrorType {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.write_str(self.as_str())
57    }
58}
59
60impl From<&str> for ApiErrorType {
61    fn from(s: &str) -> Self {
62        match s {
63            "api_error" => Self::ApiError,
64            "overloaded_error" => Self::OverloadedError,
65            "invalid_request_error" => Self::InvalidRequestError,
66            "authentication_error" => Self::AuthenticationError,
67            "rate_limit_error" => Self::RateLimitError,
68            other => Self::Unknown(other.to_string()),
69        }
70    }
71}
72
73impl Serialize for ApiErrorType {
74    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
75        serializer.serialize_str(self.as_str())
76    }
77}
78
79impl<'de> Deserialize<'de> for ApiErrorType {
80    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
81        let s = String::deserialize(deserializer)?;
82        Ok(Self::from(s.as_str()))
83    }
84}
85
86/// API error message from Anthropic.
87///
88/// When Claude Code encounters an API error (e.g., 500, 529 overloaded), it outputs
89/// a JSON message with `type: "error"`. This struct captures that error information.
90///
91/// # Example JSON
92///
93/// ```json
94/// {
95///   "type": "error",
96///   "error": {
97///     "type": "api_error",
98///     "message": "Internal server error"
99///   },
100///   "request_id": "req_011CXPC6BqUogB959LWEf52X"
101/// }
102/// ```
103///
104/// # Example
105///
106/// ```
107/// use claude_codes::ClaudeOutput;
108///
109/// let json = r#"{"type":"error","error":{"type":"api_error","message":"Internal server error"},"request_id":"req_123"}"#;
110/// let output: ClaudeOutput = serde_json::from_str(json).unwrap();
111///
112/// if let ClaudeOutput::Error(err) = output {
113///     println!("Error type: {}", err.error.error_type);
114///     println!("Message: {}", err.error.message);
115/// }
116/// ```
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118pub struct AnthropicError {
119    /// The nested error details
120    pub error: AnthropicErrorDetails,
121    /// The request ID for debugging/support
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub request_id: Option<String>,
124}
125
126impl AnthropicError {
127    /// Check if this is an overloaded error (HTTP 529)
128    pub fn is_overloaded(&self) -> bool {
129        self.error.error_type == ApiErrorType::OverloadedError
130    }
131
132    /// Check if this is a server error (HTTP 500)
133    pub fn is_server_error(&self) -> bool {
134        self.error.error_type == ApiErrorType::ApiError
135    }
136
137    /// Check if this is an invalid request error (HTTP 400)
138    pub fn is_invalid_request(&self) -> bool {
139        self.error.error_type == ApiErrorType::InvalidRequestError
140    }
141
142    /// Check if this is an authentication error (HTTP 401)
143    pub fn is_authentication_error(&self) -> bool {
144        self.error.error_type == ApiErrorType::AuthenticationError
145    }
146
147    /// Check if this is a rate limit error (HTTP 429)
148    pub fn is_rate_limited(&self) -> bool {
149        self.error.error_type == ApiErrorType::RateLimitError
150    }
151}
152
153/// Details of an Anthropic API error.
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
155pub struct AnthropicErrorDetails {
156    /// The type of error
157    #[serde(rename = "type")]
158    pub error_type: ApiErrorType,
159    /// Human-readable error message
160    pub message: String,
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::io::ClaudeOutput;
167
168    #[test]
169    fn test_deserialize_anthropic_error() {
170        let json = r#"{
171            "type": "error",
172            "error": {
173                "type": "api_error",
174                "message": "Internal server error"
175            },
176            "request_id": "req_011CXPC6BqUogB959LWEf52X"
177        }"#;
178
179        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
180        assert!(output.is_api_error());
181        assert_eq!(output.message_type(), "error");
182
183        if let ClaudeOutput::Error(err) = output {
184            assert_eq!(err.error.error_type, ApiErrorType::ApiError);
185            assert_eq!(err.error.message, "Internal server error");
186            assert_eq!(
187                err.request_id,
188                Some("req_011CXPC6BqUogB959LWEf52X".to_string())
189            );
190            assert!(err.is_server_error());
191            assert!(!err.is_overloaded());
192        } else {
193            panic!("Expected Error variant");
194        }
195    }
196
197    #[test]
198    fn test_deserialize_anthropic_overloaded_error() {
199        let json = r#"{
200            "type": "error",
201            "error": {
202                "type": "overloaded_error",
203                "message": "Overloaded"
204            }
205        }"#;
206
207        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
208
209        if let ClaudeOutput::Error(err) = output {
210            assert!(err.is_overloaded());
211            assert!(!err.is_server_error());
212            assert!(err.request_id.is_none());
213        } else {
214            panic!("Expected Error variant");
215        }
216    }
217
218    #[test]
219    fn test_deserialize_anthropic_rate_limit_error() {
220        let json = r#"{
221            "type": "error",
222            "error": {
223                "type": "rate_limit_error",
224                "message": "Rate limit exceeded"
225            },
226            "request_id": "req_456"
227        }"#;
228
229        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
230
231        if let ClaudeOutput::Error(err) = output {
232            assert!(err.is_rate_limited());
233            assert!(!err.is_overloaded());
234            assert!(!err.is_server_error());
235        } else {
236            panic!("Expected Error variant");
237        }
238    }
239
240    #[test]
241    fn test_deserialize_anthropic_authentication_error() {
242        let json = r#"{
243            "type": "error",
244            "error": {
245                "type": "authentication_error",
246                "message": "Invalid API key"
247            }
248        }"#;
249
250        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
251
252        if let ClaudeOutput::Error(err) = output {
253            assert!(err.is_authentication_error());
254        } else {
255            panic!("Expected Error variant");
256        }
257    }
258
259    #[test]
260    fn test_deserialize_anthropic_invalid_request_error() {
261        let json = r#"{
262            "type": "error",
263            "error": {
264                "type": "invalid_request_error",
265                "message": "Invalid request body"
266            }
267        }"#;
268
269        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
270
271        if let ClaudeOutput::Error(err) = output {
272            assert!(err.is_invalid_request());
273        } else {
274            panic!("Expected Error variant");
275        }
276    }
277
278    #[test]
279    fn test_anthropic_error_as_helper() {
280        let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
281        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
282
283        let err = output.as_anthropic_error();
284        assert!(err.is_some());
285        assert_eq!(err.unwrap().error.error_type, ApiErrorType::ApiError);
286
287        // Non-error should return None
288        let result_json = r#"{
289            "type": "result",
290            "subtype": "success",
291            "is_error": false,
292            "duration_ms": 100,
293            "duration_api_ms": 200,
294            "num_turns": 1,
295            "session_id": "abc",
296            "total_cost_usd": 0.01
297        }"#;
298        let result: ClaudeOutput = serde_json::from_str(result_json).unwrap();
299        assert!(result.as_anthropic_error().is_none());
300    }
301
302    #[test]
303    fn test_anthropic_error_roundtrip() {
304        let error = AnthropicError {
305            error: AnthropicErrorDetails {
306                error_type: ApiErrorType::ApiError,
307                message: "Test error".to_string(),
308            },
309            request_id: Some("req_123".to_string()),
310        };
311
312        let json = serde_json::to_string(&error).unwrap();
313        assert!(json.contains("\"type\":\"api_error\""));
314        assert!(json.contains("\"message\":\"Test error\""));
315        assert!(json.contains("\"request_id\":\"req_123\""));
316
317        let parsed: AnthropicError = serde_json::from_str(&json).unwrap();
318        assert_eq!(parsed, error);
319    }
320
321    #[test]
322    fn test_anthropic_error_session_id_is_none() {
323        let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
324        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
325        assert!(output.session_id().is_none());
326    }
327}