1use reqwest::StatusCode;
2use serde::Deserialize;
3use serde::Serialize;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum AnthropicError {
9 #[error("HTTP error: {0}")]
11 Reqwest(#[from] reqwest::Error),
12
13 #[error("API error: {0:?}")]
15 Api(ApiErrorObject),
16
17 #[error("Invalid configuration: {0}")]
19 Config(String),
20
21 #[error("Serialization error: {0}")]
23 Serde(String),
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ApiErrorObject {
29 pub r#type: Option<String>,
31 pub message: String,
33 pub request_id: Option<String>,
35 pub code: Option<String>,
37 #[serde(skip)]
39 pub status_code: Option<u16>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43struct ErrorEnvelope {
44 error: ApiErrorObject,
45}
46
47impl AnthropicError {
48 #[must_use]
53 pub fn is_retryable(&self) -> bool {
54 match self {
55 Self::Api(obj) => obj
56 .status_code
57 .is_some_and(crate::retry::is_retryable_status),
58 Self::Reqwest(e) => e.is_timeout() || e.is_connect(),
59 Self::Config(_) | Self::Serde(_) => false,
60 }
61 }
62}
63
64#[must_use]
68pub fn map_deser(e: &serde_json::Error, body: &[u8]) -> AnthropicError {
69 let snippet = String::from_utf8_lossy(&body[..body.len().min(400)]).to_string();
70 AnthropicError::Serde(format!("{e}: {snippet}"))
71}
72
73#[must_use]
77pub fn deserialize_api_error(status: StatusCode, body: &[u8]) -> AnthropicError {
78 let status_code = Some(status.as_u16());
79
80 if let Ok(envelope) = serde_json::from_slice::<ErrorEnvelope>(body) {
81 let mut error = envelope.error;
82 error.status_code = status_code;
83 return AnthropicError::Api(error);
84 }
85
86 AnthropicError::Api(ApiErrorObject {
88 r#type: Some(format!("http_{}", status.as_u16())),
89 message: String::from_utf8_lossy(body).into_owned(),
90 request_id: None,
91 code: None,
92 status_code,
93 })
94}