claude_codes/io/
errors.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
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, PartialEq, Eq, Hash)]
26pub enum ApiErrorType {
27 ApiError,
29 OverloadedError,
31 InvalidRequestError,
33 AuthenticationError,
35 RateLimitError,
37 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118pub struct AnthropicError {
119 pub error: AnthropicErrorDetails,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub request_id: Option<String>,
124}
125
126impl AnthropicError {
127 pub fn is_overloaded(&self) -> bool {
129 self.error.error_type == ApiErrorType::OverloadedError
130 }
131
132 pub fn is_server_error(&self) -> bool {
134 self.error.error_type == ApiErrorType::ApiError
135 }
136
137 pub fn is_invalid_request(&self) -> bool {
139 self.error.error_type == ApiErrorType::InvalidRequestError
140 }
141
142 pub fn is_authentication_error(&self) -> bool {
144 self.error.error_type == ApiErrorType::AuthenticationError
145 }
146
147 pub fn is_rate_limited(&self) -> bool {
149 self.error.error_type == ApiErrorType::RateLimitError
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
155pub struct AnthropicErrorDetails {
156 #[serde(rename = "type")]
158 pub error_type: ApiErrorType,
159 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 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}