claude_codes/io/
errors.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4
5#[derive(Debug, Clone)]
12pub struct ParseError {
13 pub raw_line: String,
15 pub raw_json: Option<Value>,
17 pub error_message: String,
19}
20
21impl fmt::Display for ParseError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(
24 f,
25 "Failed to parse ClaudeOutput: {} (raw: {})",
26 self.error_message, self.raw_line
27 )
28 }
29}
30
31impl std::error::Error for ParseError {}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum ApiErrorType {
38 ApiError,
40 OverloadedError,
42 InvalidRequestError,
44 AuthenticationError,
46 RateLimitError,
48 Unknown(String),
50}
51
52impl ApiErrorType {
53 pub fn as_str(&self) -> &str {
54 match self {
55 Self::ApiError => "api_error",
56 Self::OverloadedError => "overloaded_error",
57 Self::InvalidRequestError => "invalid_request_error",
58 Self::AuthenticationError => "authentication_error",
59 Self::RateLimitError => "rate_limit_error",
60 Self::Unknown(s) => s.as_str(),
61 }
62 }
63}
64
65impl fmt::Display for ApiErrorType {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.write_str(self.as_str())
68 }
69}
70
71impl From<&str> for ApiErrorType {
72 fn from(s: &str) -> Self {
73 match s {
74 "api_error" => Self::ApiError,
75 "overloaded_error" => Self::OverloadedError,
76 "invalid_request_error" => Self::InvalidRequestError,
77 "authentication_error" => Self::AuthenticationError,
78 "rate_limit_error" => Self::RateLimitError,
79 other => Self::Unknown(other.to_string()),
80 }
81 }
82}
83
84impl Serialize for ApiErrorType {
85 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
86 serializer.serialize_str(self.as_str())
87 }
88}
89
90impl<'de> Deserialize<'de> for ApiErrorType {
91 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
92 let s = String::deserialize(deserializer)?;
93 Ok(Self::from(s.as_str()))
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub struct AnthropicError {
130 pub error: AnthropicErrorDetails,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub request_id: Option<String>,
135}
136
137impl AnthropicError {
138 pub fn is_overloaded(&self) -> bool {
140 self.error.error_type == ApiErrorType::OverloadedError
141 }
142
143 pub fn is_server_error(&self) -> bool {
145 self.error.error_type == ApiErrorType::ApiError
146 }
147
148 pub fn is_invalid_request(&self) -> bool {
150 self.error.error_type == ApiErrorType::InvalidRequestError
151 }
152
153 pub fn is_authentication_error(&self) -> bool {
155 self.error.error_type == ApiErrorType::AuthenticationError
156 }
157
158 pub fn is_rate_limited(&self) -> bool {
160 self.error.error_type == ApiErrorType::RateLimitError
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
166pub struct AnthropicErrorDetails {
167 #[serde(rename = "type")]
169 pub error_type: ApiErrorType,
170 pub message: String,
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::io::ClaudeOutput;
178
179 #[test]
180 fn test_deserialize_anthropic_error() {
181 let json = r#"{
182 "type": "error",
183 "error": {
184 "type": "api_error",
185 "message": "Internal server error"
186 },
187 "request_id": "req_011CXPC6BqUogB959LWEf52X"
188 }"#;
189
190 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
191 assert!(output.is_api_error());
192 assert_eq!(output.message_type(), "error");
193
194 if let ClaudeOutput::Error(err) = output {
195 assert_eq!(err.error.error_type, ApiErrorType::ApiError);
196 assert_eq!(err.error.message, "Internal server error");
197 assert_eq!(
198 err.request_id,
199 Some("req_011CXPC6BqUogB959LWEf52X".to_string())
200 );
201 assert!(err.is_server_error());
202 assert!(!err.is_overloaded());
203 } else {
204 panic!("Expected Error variant");
205 }
206 }
207
208 #[test]
209 fn test_deserialize_anthropic_overloaded_error() {
210 let json = r#"{
211 "type": "error",
212 "error": {
213 "type": "overloaded_error",
214 "message": "Overloaded"
215 }
216 }"#;
217
218 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
219
220 if let ClaudeOutput::Error(err) = output {
221 assert!(err.is_overloaded());
222 assert!(!err.is_server_error());
223 assert!(err.request_id.is_none());
224 } else {
225 panic!("Expected Error variant");
226 }
227 }
228
229 #[test]
230 fn test_deserialize_anthropic_rate_limit_error() {
231 let json = r#"{
232 "type": "error",
233 "error": {
234 "type": "rate_limit_error",
235 "message": "Rate limit exceeded"
236 },
237 "request_id": "req_456"
238 }"#;
239
240 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
241
242 if let ClaudeOutput::Error(err) = output {
243 assert!(err.is_rate_limited());
244 assert!(!err.is_overloaded());
245 assert!(!err.is_server_error());
246 } else {
247 panic!("Expected Error variant");
248 }
249 }
250
251 #[test]
252 fn test_deserialize_anthropic_authentication_error() {
253 let json = r#"{
254 "type": "error",
255 "error": {
256 "type": "authentication_error",
257 "message": "Invalid API key"
258 }
259 }"#;
260
261 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
262
263 if let ClaudeOutput::Error(err) = output {
264 assert!(err.is_authentication_error());
265 } else {
266 panic!("Expected Error variant");
267 }
268 }
269
270 #[test]
271 fn test_deserialize_anthropic_invalid_request_error() {
272 let json = r#"{
273 "type": "error",
274 "error": {
275 "type": "invalid_request_error",
276 "message": "Invalid request body"
277 }
278 }"#;
279
280 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
281
282 if let ClaudeOutput::Error(err) = output {
283 assert!(err.is_invalid_request());
284 } else {
285 panic!("Expected Error variant");
286 }
287 }
288
289 #[test]
290 fn test_anthropic_error_as_helper() {
291 let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
292 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
293
294 let err = output.as_anthropic_error();
295 assert!(err.is_some());
296 assert_eq!(err.unwrap().error.error_type, ApiErrorType::ApiError);
297
298 let result_json = r#"{
300 "type": "result",
301 "subtype": "success",
302 "is_error": false,
303 "duration_ms": 100,
304 "duration_api_ms": 200,
305 "num_turns": 1,
306 "session_id": "abc",
307 "total_cost_usd": 0.01
308 }"#;
309 let result: ClaudeOutput = serde_json::from_str(result_json).unwrap();
310 assert!(result.as_anthropic_error().is_none());
311 }
312
313 #[test]
314 fn test_anthropic_error_roundtrip() {
315 let error = AnthropicError {
316 error: AnthropicErrorDetails {
317 error_type: ApiErrorType::ApiError,
318 message: "Test error".to_string(),
319 },
320 request_id: Some("req_123".to_string()),
321 };
322
323 let json = serde_json::to_string(&error).unwrap();
324 assert!(json.contains("\"type\":\"api_error\""));
325 assert!(json.contains("\"message\":\"Test error\""));
326 assert!(json.contains("\"request_id\":\"req_123\""));
327
328 let parsed: AnthropicError = serde_json::from_str(&json).unwrap();
329 assert_eq!(parsed, error);
330 }
331
332 #[test]
333 fn test_anthropic_error_session_id_is_none() {
334 let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
335 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
336 assert!(output.session_id().is_none());
337 }
338}