claude_codes/io/
errors.rs1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::fmt;
4
5#[derive(Debug, Clone)]
7pub struct ParseError {
8 pub raw_json: Value,
10 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub struct AnthropicError {
55 pub error: AnthropicErrorDetails,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub request_id: Option<String>,
60}
61
62impl AnthropicError {
63 pub fn is_overloaded(&self) -> bool {
65 self.error.error_type == "overloaded_error"
66 }
67
68 pub fn is_server_error(&self) -> bool {
70 self.error.error_type == "api_error"
71 }
72
73 pub fn is_invalid_request(&self) -> bool {
75 self.error.error_type == "invalid_request_error"
76 }
77
78 pub fn is_authentication_error(&self) -> bool {
80 self.error.error_type == "authentication_error"
81 }
82
83 pub fn is_rate_limited(&self) -> bool {
85 self.error.error_type == "rate_limit_error"
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct AnthropicErrorDetails {
92 #[serde(rename = "type")]
94 pub error_type: String,
95 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 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}