use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::fmt;
#[derive(Debug, Clone)]
pub struct ParseError {
pub raw_line: String,
pub raw_json: Option<Value>,
pub error_message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Failed to parse ClaudeOutput: {} (raw: {})",
self.error_message, self.raw_line
)
}
}
impl std::error::Error for ParseError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ApiErrorType {
ApiError,
OverloadedError,
InvalidRequestError,
AuthenticationError,
RateLimitError,
Unknown(String),
}
impl ApiErrorType {
pub fn as_str(&self) -> &str {
match self {
Self::ApiError => "api_error",
Self::OverloadedError => "overloaded_error",
Self::InvalidRequestError => "invalid_request_error",
Self::AuthenticationError => "authentication_error",
Self::RateLimitError => "rate_limit_error",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for ApiErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for ApiErrorType {
fn from(s: &str) -> Self {
match s {
"api_error" => Self::ApiError,
"overloaded_error" => Self::OverloadedError,
"invalid_request_error" => Self::InvalidRequestError,
"authentication_error" => Self::AuthenticationError,
"rate_limit_error" => Self::RateLimitError,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for ApiErrorType {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ApiErrorType {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AnthropicError {
pub error: AnthropicErrorDetails,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
}
impl AnthropicError {
pub fn is_overloaded(&self) -> bool {
self.error.error_type == ApiErrorType::OverloadedError
}
pub fn is_server_error(&self) -> bool {
self.error.error_type == ApiErrorType::ApiError
}
pub fn is_invalid_request(&self) -> bool {
self.error.error_type == ApiErrorType::InvalidRequestError
}
pub fn is_authentication_error(&self) -> bool {
self.error.error_type == ApiErrorType::AuthenticationError
}
pub fn is_rate_limited(&self) -> bool {
self.error.error_type == ApiErrorType::RateLimitError
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AnthropicErrorDetails {
#[serde(rename = "type")]
pub error_type: ApiErrorType,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::ClaudeOutput;
#[test]
fn test_deserialize_anthropic_error() {
let json = r#"{
"type": "error",
"error": {
"type": "api_error",
"message": "Internal server error"
},
"request_id": "req_011CXPC6BqUogB959LWEf52X"
}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
assert!(output.is_api_error());
assert_eq!(output.message_type(), "error");
if let ClaudeOutput::Error(err) = output {
assert_eq!(err.error.error_type, ApiErrorType::ApiError);
assert_eq!(err.error.message, "Internal server error");
assert_eq!(
err.request_id,
Some("req_011CXPC6BqUogB959LWEf52X".to_string())
);
assert!(err.is_server_error());
assert!(!err.is_overloaded());
} else {
panic!("Expected Error variant");
}
}
#[test]
fn test_deserialize_anthropic_overloaded_error() {
let json = r#"{
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
if let ClaudeOutput::Error(err) = output {
assert!(err.is_overloaded());
assert!(!err.is_server_error());
assert!(err.request_id.is_none());
} else {
panic!("Expected Error variant");
}
}
#[test]
fn test_deserialize_anthropic_rate_limit_error() {
let json = r#"{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded"
},
"request_id": "req_456"
}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
if let ClaudeOutput::Error(err) = output {
assert!(err.is_rate_limited());
assert!(!err.is_overloaded());
assert!(!err.is_server_error());
} else {
panic!("Expected Error variant");
}
}
#[test]
fn test_deserialize_anthropic_authentication_error() {
let json = r#"{
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
if let ClaudeOutput::Error(err) = output {
assert!(err.is_authentication_error());
} else {
panic!("Expected Error variant");
}
}
#[test]
fn test_deserialize_anthropic_invalid_request_error() {
let json = r#"{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Invalid request body"
}
}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
if let ClaudeOutput::Error(err) = output {
assert!(err.is_invalid_request());
} else {
panic!("Expected Error variant");
}
}
#[test]
fn test_anthropic_error_as_helper() {
let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
let err = output.as_anthropic_error();
assert!(err.is_some());
assert_eq!(err.unwrap().error.error_type, ApiErrorType::ApiError);
let result_json = r#"{
"type": "result",
"subtype": "success",
"is_error": false,
"duration_ms": 100,
"duration_api_ms": 200,
"num_turns": 1,
"session_id": "abc",
"total_cost_usd": 0.01
}"#;
let result: ClaudeOutput = serde_json::from_str(result_json).unwrap();
assert!(result.as_anthropic_error().is_none());
}
#[test]
fn test_anthropic_error_roundtrip() {
let error = AnthropicError {
error: AnthropicErrorDetails {
error_type: ApiErrorType::ApiError,
message: "Test error".to_string(),
},
request_id: Some("req_123".to_string()),
};
let json = serde_json::to_string(&error).unwrap();
assert!(json.contains("\"type\":\"api_error\""));
assert!(json.contains("\"message\":\"Test error\""));
assert!(json.contains("\"request_id\":\"req_123\""));
let parsed: AnthropicError = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, error);
}
#[test]
fn test_anthropic_error_session_id_is_none() {
let json = r#"{"type":"error","error":{"type":"api_error","message":"Error"}}"#;
let output: ClaudeOutput = serde_json::from_str(json).unwrap();
assert!(output.session_id().is_none());
}
}