Skip to main content

anthropic_async/
error.rs

1use reqwest::StatusCode;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5/// Errors that can occur when using the Anthropic API client
6#[derive(Debug, Error)]
7pub enum AnthropicError {
8    /// HTTP request error
9    #[error("HTTP error: {0}")]
10    Reqwest(#[from] reqwest::Error),
11
12    /// API error returned by Anthropic
13    #[error("API error: {0:?}")]
14    Api(ApiErrorObject),
15
16    /// Configuration error (e.g., missing credentials)
17    #[error("Invalid configuration: {0}")]
18    Config(String),
19
20    /// Serialization/deserialization error
21    #[error("Serialization error: {0}")]
22    Serde(String),
23}
24
25/// API error object from Anthropic
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ApiErrorObject {
28    /// Error type (e.g., "`invalid_request_error`", "`rate_limit_error`")
29    pub r#type: Option<String>,
30    /// Human-readable error message
31    pub message: String,
32    /// Request ID for debugging
33    pub request_id: Option<String>,
34    /// Error code
35    pub code: Option<String>,
36    /// HTTP status code (not serialized, used for retry logic)
37    #[serde(skip)]
38    pub status_code: Option<u16>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42struct ErrorEnvelope {
43    error: ApiErrorObject,
44}
45
46impl AnthropicError {
47    /// Determines if this error is retryable
48    ///
49    /// Retryable errors include rate limits (429), timeouts (408),
50    /// conflicts (409), and server errors (5xx).
51    #[must_use]
52    pub fn is_retryable(&self) -> bool {
53        match self {
54            Self::Api(obj) => obj
55                .status_code
56                .is_some_and(crate::retry::is_retryable_status),
57            Self::Reqwest(e) => e.is_timeout() || e.is_connect(),
58            Self::Config(_) | Self::Serde(_) => false,
59        }
60    }
61}
62
63/// Maps a serde deserialization error to an `AnthropicError` with context
64///
65/// Includes a snippet of the response body for debugging.
66#[must_use]
67pub fn map_deser(e: &serde_json::Error, body: &[u8]) -> AnthropicError {
68    let snippet = String::from_utf8_lossy(&body[..body.len().min(400)]).to_string();
69    AnthropicError::Serde(format!("{e}: {snippet}"))
70}
71
72/// Deserializes an API error from the response body
73///
74/// Attempts to parse the error as JSON, falling back to plain text on failure.
75#[must_use]
76pub fn deserialize_api_error(status: StatusCode, body: &[u8]) -> AnthropicError {
77    let status_code = Some(status.as_u16());
78
79    if let Ok(envelope) = serde_json::from_slice::<ErrorEnvelope>(body) {
80        let mut error = envelope.error;
81        error.status_code = status_code;
82        return AnthropicError::Api(error);
83    }
84
85    // Server may return plain text on 5xx
86    AnthropicError::Api(ApiErrorObject {
87        r#type: Some(format!("http_{}", status.as_u16())),
88        message: String::from_utf8_lossy(body).into_owned(),
89        request_id: None,
90        code: None,
91        status_code,
92    })
93}