Skip to main content

claude_codes/io/
errors.rs

1use serde::{Deserialize, Serialize};
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/// API error message from Anthropic.
23///
24/// When Claude Code encounters an API error (e.g., 500, 529 overloaded), it outputs
25/// a JSON message with `type: "error"`. This struct captures that error information.
26///
27/// # Example JSON
28///
29/// ```json
30/// {
31///   "type": "error",
32///   "error": {
33///     "type": "api_error",
34///     "message": "Internal server error"
35///   },
36///   "request_id": "req_011CXPC6BqUogB959LWEf52X"
37/// }
38/// ```
39///
40/// # Example
41///
42/// ```
43/// use claude_codes::ClaudeOutput;
44///
45/// let json = r#"{"type":"error","error":{"type":"api_error","message":"Internal server error"},"request_id":"req_123"}"#;
46/// let output: ClaudeOutput = serde_json::from_str(json).unwrap();
47///
48/// if let ClaudeOutput::Error(err) = output {
49///     println!("Error type: {}", err.error.error_type);
50///     println!("Message: {}", err.error.message);
51/// }
52/// ```
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub struct AnthropicError {
55    /// The nested error details
56    pub error: AnthropicErrorDetails,
57    /// The request ID for debugging/support
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub request_id: Option<String>,
60}
61
62impl AnthropicError {
63    /// Check if this is an overloaded error (HTTP 529)
64    pub fn is_overloaded(&self) -> bool {
65        self.error.error_type == "overloaded_error"
66    }
67
68    /// Check if this is a server error (HTTP 500)
69    pub fn is_server_error(&self) -> bool {
70        self.error.error_type == "api_error"
71    }
72
73    /// Check if this is an invalid request error (HTTP 400)
74    pub fn is_invalid_request(&self) -> bool {
75        self.error.error_type == "invalid_request_error"
76    }
77
78    /// Check if this is an authentication error (HTTP 401)
79    pub fn is_authentication_error(&self) -> bool {
80        self.error.error_type == "authentication_error"
81    }
82
83    /// Check if this is a rate limit error (HTTP 429)
84    pub fn is_rate_limited(&self) -> bool {
85        self.error.error_type == "rate_limit_error"
86    }
87}
88
89/// Details of an Anthropic API error.
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct AnthropicErrorDetails {
92    /// The type of error (e.g., "api_error", "overloaded_error", "invalid_request_error")
93    #[serde(rename = "type")]
94    pub error_type: String,
95    /// Human-readable error message
96    pub message: String,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::io::ClaudeOutput;
103
104    #[test]
105    fn test_deserialize_anthropic_error() {
106        let json = r#"{
107            "type": "error",
108            "error": {
109                "type": "api_error",
110                "message": "Internal server error"
111            },
112            "request_id": "req_011CXPC6BqUogB959LWEf52X"
113        }"#;
114
115        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
116        assert!(output.is_api_error());
117        assert_eq!(output.message_type(), "error");
118
119        if let ClaudeOutput::Error(err) = output {
120            assert_eq!(err.error.error_type, "api_error");
121            assert_eq!(err.error.message, "Internal server error");
122            assert_eq!(
123                err.request_id,
124                Some("req_011CXPC6BqUogB959LWEf52X".to_string())
125            );
126            assert!(err.is_server_error());
127            assert!(!err.is_overloaded());
128        } else {
129            panic!("Expected Error variant");
130        }
131    }
132
133    #[test]
134    fn test_deserialize_anthropic_overloaded_error() {
135        let json = r#"{
136            "type": "error",
137            "error": {
138                "type": "overloaded_error",
139                "message": "Overloaded"
140            }
141        }"#;
142
143        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
144
145        if let ClaudeOutput::Error(err) = output {
146            assert!(err.is_overloaded());
147            assert!(!err.is_server_error());
148            assert!(err.request_id.is_none());
149        } else {
150            panic!("Expected Error variant");
151        }
152    }
153
154    #[test]
155    fn test_deserialize_anthropic_rate_limit_error() {
156        let json = r#"{
157            "type": "error",
158            "error": {
159                "type": "rate_limit_error",
160                "message": "Rate limit exceeded"
161            },
162            "request_id": "req_456"
163        }"#;
164
165        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
166
167        if let ClaudeOutput::Error(err) = output {
168            assert!(err.is_rate_limited());
169            assert!(!err.is_overloaded());
170            assert!(!err.is_server_error());
171        } else {
172            panic!("Expected Error variant");
173        }
174    }
175
176    #[test]
177    fn test_deserialize_anthropic_authentication_error() {
178        let json = r#"{
179            "type": "error",
180            "error": {
181                "type": "authentication_error",
182                "message": "Invalid API key"
183            }
184        }"#;
185
186        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
187
188        if let ClaudeOutput::Error(err) = output {
189            assert!(err.is_authentication_error());
190        } else {
191            panic!("Expected Error variant");
192        }
193    }
194
195    #[test]
196    fn test_deserialize_anthropic_invalid_request_error() {
197        let json = r#"{
198            "type": "error",
199            "error": {
200                "type": "invalid_request_error",
201                "message": "Invalid request body"
202            }
203        }"#;
204
205        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
206
207        if let ClaudeOutput::Error(err) = output {
208            assert!(err.is_invalid_request());
209        } else {
210            panic!("Expected Error variant");
211        }
212    }
213
214    #[test]
215    fn test_anthropic_error_as_helper() {
216        let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
217        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
218
219        let err = output.as_anthropic_error();
220        assert!(err.is_some());
221        assert_eq!(err.unwrap().error.error_type, "api_error");
222
223        // Non-error should return None
224        let result_json = r#"{
225            "type": "result",
226            "subtype": "success",
227            "is_error": false,
228            "duration_ms": 100,
229            "duration_api_ms": 200,
230            "num_turns": 1,
231            "session_id": "abc",
232            "total_cost_usd": 0.01
233        }"#;
234        let result: ClaudeOutput = serde_json::from_str(result_json).unwrap();
235        assert!(result.as_anthropic_error().is_none());
236    }
237
238    #[test]
239    fn test_anthropic_error_roundtrip() {
240        let error = AnthropicError {
241            error: AnthropicErrorDetails {
242                error_type: "api_error".to_string(),
243                message: "Test error".to_string(),
244            },
245            request_id: Some("req_123".to_string()),
246        };
247
248        let json = serde_json::to_string(&error).unwrap();
249        assert!(json.contains("\"type\":\"api_error\""));
250        assert!(json.contains("\"message\":\"Test error\""));
251        assert!(json.contains("\"request_id\":\"req_123\""));
252
253        let parsed: AnthropicError = serde_json::from_str(&json).unwrap();
254        assert_eq!(parsed, error);
255    }
256
257    #[test]
258    fn test_anthropic_error_session_id_is_none() {
259        let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
260        let output: ClaudeOutput = serde_json::from_str(json).unwrap();
261        assert!(output.session_id().is_none());
262    }
263}