Skip to main content

anthropic_async/
error.rs

1use reqwest::StatusCode;
2use serde::Deserialize;
3use serde::Serialize;
4use thiserror::Error;
5
6/// Errors that can occur when using the Anthropic API client
7#[derive(Debug, Error)]
8pub enum AnthropicError {
9    /// HTTP request error
10    #[error("HTTP error: {0}")]
11    Reqwest(#[from] reqwest::Error),
12
13    /// API error returned by Anthropic
14    #[error("API error: {0:?}")]
15    Api(ApiErrorObject),
16
17    /// Configuration error (e.g., missing credentials)
18    #[error("Invalid configuration: {0}")]
19    Config(String),
20
21    /// Serialization/deserialization error
22    #[error("Serialization error: {0}")]
23    Serde(String),
24}
25
26/// API error object from Anthropic
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ApiErrorObject {
29    /// Error type (e.g., "`invalid_request_error`", "`rate_limit_error`")
30    pub r#type: Option<String>,
31    /// Human-readable error message
32    pub message: String,
33    /// Request ID for debugging
34    pub request_id: Option<String>,
35    /// Error code
36    pub code: Option<String>,
37    /// HTTP status code (not serialized, used for retry logic)
38    #[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    /// Determines if this error is retryable
49    ///
50    /// Retryable errors include rate limits (429), timeouts (408),
51    /// conflicts (409), and server errors (5xx).
52    #[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/// Maps a serde deserialization error to an `AnthropicError` with context
65///
66/// Includes a snippet of the response body for debugging.
67#[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/// Deserializes an API error from the response body
74///
75/// Attempts to parse the error as JSON, falling back to plain text on failure.
76#[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    // Server may return plain text on 5xx
87    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}