use serde::Deserialize;
use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum ClaudeApiError {
#[error("Invalid request (400): {message}")]
InvalidRequest { message: String },
#[error("Authentication failed (401): {message}")]
AuthenticationError { message: String },
#[error("Permission denied (403): {message}")]
PermissionDenied { message: String },
#[error("Not found (404): {message}")]
NotFound { message: String },
#[error("Request too large (413): {message}")]
RequestTooLarge { message: String },
#[error("Rate limited (429): {message}")]
RateLimited { message: String },
#[error("Internal server error (500): {message}")]
InternalError { message: String },
#[error("API overloaded (529): {message}")]
Overloaded { message: String },
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("JSON parse error: {0}")]
JsonParse(#[from] serde_json::Error),
#[error("Stream error: {message}")]
StreamError { message: String },
#[error("Unexpected API error ({status}): {message}")]
Unknown { status: u16, message: String },
}
impl ClaudeApiError {
pub fn is_retryable(&self) -> bool {
matches!(
self,
ClaudeApiError::RateLimited { .. }
| ClaudeApiError::InternalError { .. }
| ClaudeApiError::Overloaded { .. }
)
}
}
#[derive(Debug, Deserialize)]
pub struct ApiErrorResponse {
#[serde(rename = "type")]
pub response_type: String,
pub error: ApiErrorBody,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ApiErrorBody {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
}
impl fmt::Display for ApiErrorBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.error_type, self.message)
}
}
impl ClaudeApiError {
pub fn from_response(status: u16, body: &str) -> Self {
let message = serde_json::from_str::<ApiErrorResponse>(body)
.map(|r| r.error.message)
.unwrap_or_else(|_| body.to_string());
match status {
400 => ClaudeApiError::InvalidRequest { message },
401 => ClaudeApiError::AuthenticationError { message },
403 => ClaudeApiError::PermissionDenied { message },
404 => ClaudeApiError::NotFound { message },
413 => ClaudeApiError::RequestTooLarge { message },
429 => ClaudeApiError::RateLimited { message },
500 => ClaudeApiError::InternalError { message },
529 => ClaudeApiError::Overloaded { message },
_ => ClaudeApiError::Unknown { status, message },
}
}
}