Skip to main content

claude_sdk/
error.rs

1//! Error types for the Claude SDK.
2//!
3//! This module provides comprehensive error handling for all Claude API operations,
4//! with support for retryable errors and rate limit information.
5//!
6//! # Error Types
7//!
8//! The main error enum covers all possible failure modes:
9//!
10//! | Error | Description | Retryable |
11//! |-------|-------------|-----------|
12//! | [`Error::Api`] | API returned an error response | No |
13//! | [`Error::RateLimit`] | Rate limit exceeded (429) | Yes |
14//! | [`Error::Server`] | Server error (5xx) | Yes |
15//! | [`Error::Network`] | Connection/network failure | Yes |
16//! | [`Error::Authentication`] | Invalid API key | No |
17//! | [`Error::InvalidRequest`] | Malformed request | No |
18//! | [`Error::Http`] | HTTP client error | Depends |
19//! | [`Error::Json`] | JSON serialization error | No |
20//! | [`Error::StreamParse`] | SSE parsing error | No |
21//!
22//! # Example: Basic Error Handling
23//!
24//! ```rust,no_run
25//! use claude_sdk::{ClaudeClient, MessagesRequest, Message, Error};
26//!
27//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
28//! let client = ClaudeClient::anthropic(std::env::var("ANTHROPIC_API_KEY")?);
29//!
30//! let request = MessagesRequest::new(
31//!     "claude-sonnet-4-5-20250929",
32//!     1024,
33//!     vec![Message::user("Hello!")],
34//! );
35//!
36//! match client.send_message(request).await {
37//!     Ok(response) => {
38//!         println!("Success: {:?}", response.content);
39//!     }
40//!     Err(Error::RateLimit { retry_after, message }) => {
41//!         println!("Rate limited: {}", message);
42//!         if let Some(seconds) = retry_after {
43//!             println!("Retry after {} seconds", seconds);
44//!         }
45//!     }
46//!     Err(Error::Authentication(msg)) => {
47//!         println!("Auth failed: {} - check your API key", msg);
48//!     }
49//!     Err(Error::Api { status, message, .. }) => {
50//!         println!("API error ({}): {}", status, message);
51//!     }
52//!     Err(e) if e.is_retryable() => {
53//!         println!("Retryable error: {}", e);
54//!     }
55//!     Err(e) => {
56//!         println!("Fatal error: {}", e);
57//!     }
58//! }
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! # Example: Retry with Backoff
64//!
65//! ```rust,no_run
66//! use claude_sdk::{ClaudeClient, MessagesRequest, Message, Error};
67//! use std::time::Duration;
68//!
69//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
70//! let client = ClaudeClient::anthropic(std::env::var("ANTHROPIC_API_KEY")?);
71//! let request = MessagesRequest::new(
72//!     "claude-sonnet-4-5-20250929",
73//!     1024,
74//!     vec![Message::user("Hello!")],
75//! );
76//!
77//! // Simple retry loop
78//! let mut attempts = 0;
79//! let max_attempts = 3;
80//!
81//! loop {
82//!     match client.send_message(request.clone()).await {
83//!         Ok(response) => {
84//!             println!("Success!");
85//!             break;
86//!         }
87//!         Err(ref e) if e.is_retryable() && attempts < max_attempts => {
88//!             attempts += 1;
89//!             let delay = e.retry_after().unwrap_or(2u64.pow(attempts));
90//!             println!("Retrying in {}s (attempt {})", delay, attempts);
91//!             tokio::time::sleep(Duration::from_secs(delay)).await;
92//!         }
93//!         Err(e) => return Err(e.into()),
94//!     }
95//! }
96//! # Ok(())
97//! # }
98//! ```
99//!
100//! # Using the Built-in Retry Logic
101//!
102//! For production use, prefer the `send_message_with_retry` method on `ClaudeClient` or
103//! the [`retry`](crate::retry) module:
104//!
105//! ```rust,no_run
106//! use claude_sdk::{ClaudeClient, MessagesRequest, Message};
107//! use claude_sdk::retry::RetryConfig;
108//!
109//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
110//! let client = ClaudeClient::anthropic(std::env::var("ANTHROPIC_API_KEY")?);
111//!
112//! let request = MessagesRequest::new(
113//!     "claude-sonnet-4-5-20250929",
114//!     1024,
115//!     vec![Message::user("Hello!")],
116//! );
117//!
118//! let config = RetryConfig::default()
119//!     .with_max_attempts(3);
120//!
121//! let response = client.send_message_with_retry(request, config).await?;
122//! # Ok(())
123//! # }
124//! ```
125
126use thiserror::Error;
127
128/// Result type alias using the SDK's error type.
129///
130/// This is the standard return type for all fallible SDK operations.
131///
132/// # Example
133///
134/// ```rust
135/// use claude_sdk::error::Result;
136/// use claude_sdk::MessagesResponse;
137///
138/// async fn my_function() -> Result<MessagesResponse> {
139///     // ... implementation
140///     # todo!()
141/// }
142/// ```
143pub type Result<T> = std::result::Result<T, Error>;
144
145/// Main error type for Claude SDK operations.
146///
147/// This enum covers all possible error conditions when using the SDK.
148/// Use [`Error::is_retryable`] to check if an error can be retried.
149///
150/// # Example
151///
152/// ```rust,no_run
153/// use claude_sdk::Error;
154///
155/// fn handle_error(err: Error) {
156///     if err.is_retryable() {
157///         println!("Can retry after {:?}s", err.retry_after());
158///     } else {
159///         println!("Fatal error: {}", err);
160///     }
161/// }
162/// ```
163#[derive(Error, Debug)]
164pub enum Error {
165    /// HTTP request failed.
166    ///
167    /// This wraps errors from the underlying HTTP client (reqwest).
168    /// May be retryable depending on the specific error.
169    #[error("HTTP request failed: {0}")]
170    Http(#[from] reqwest::Error),
171
172    /// Failed to parse JSON response.
173    ///
174    /// This typically indicates a bug or API contract change.
175    #[error("JSON parse error: {0}")]
176    Json(#[from] serde_json::Error),
177
178    /// API returned an error response.
179    ///
180    /// Contains the HTTP status code and error message from the API.
181    /// Check `error_type` for specific error categories like "invalid_request_error".
182    #[error("API error ({status}): {message}")]
183    Api {
184        /// HTTP status code
185        status: u16,
186        /// Error message from the API
187        message: String,
188        /// Error type (e.g., "invalid_request_error", "authentication_error")
189        error_type: Option<String>,
190    },
191
192    /// Rate limit exceeded (HTTP 429).
193    ///
194    /// The API has rate-limited your request. Check `retry_after` for the
195    /// recommended wait time before retrying.
196    #[error("Rate limit exceeded. Retry after: {retry_after:?}")]
197    RateLimit {
198        /// Seconds to wait before retrying (from Retry-After header)
199        retry_after: Option<u64>,
200        /// Error message from the API
201        message: String,
202    },
203
204    /// Invalid request.
205    ///
206    /// The request was malformed or missing required fields.
207    /// This is not retryable - fix the request before trying again.
208    #[error("Invalid request: {0}")]
209    InvalidRequest(String),
210
211    /// Authentication failed.
212    ///
213    /// The API key is invalid, expired, or missing required permissions.
214    /// Not retryable - verify your API key configuration.
215    #[error("Authentication failed: {0}")]
216    Authentication(String),
217
218    /// Server error (HTTP 5xx).
219    ///
220    /// The API server encountered an internal error. These are typically
221    /// transient and can be retried with exponential backoff.
222    #[error("Server error ({status}): {message}")]
223    Server {
224        /// HTTP status code (500-599)
225        status: u16,
226        /// Error message
227        message: String,
228    },
229
230    /// Network error.
231    ///
232    /// Connection failed, timed out, or was interrupted.
233    /// Usually retryable after a brief delay.
234    #[error("Network error: {0}")]
235    Network(String),
236
237    /// SSE stream parsing error.
238    ///
239    /// Failed to parse a server-sent event during streaming.
240    /// May indicate a malformed response or connection issue.
241    #[error("Stream parsing error: {0}")]
242    StreamParse(String),
243}
244
245impl Error {
246    /// Check if this error is retryable.
247    ///
248    /// Returns `true` for transient errors that may succeed on retry:
249    /// - Rate limits (with backoff)
250    /// - Server errors (5xx)
251    /// - Network errors
252    ///
253    /// # Example
254    ///
255    /// ```rust
256    /// use claude_sdk::Error;
257    ///
258    /// fn should_retry(err: &Error) -> bool {
259    ///     err.is_retryable()
260    /// }
261    /// ```
262    pub fn is_retryable(&self) -> bool {
263        match self {
264            Error::RateLimit { .. } => true,
265            Error::Server { status, .. } => *status >= 500,
266            Error::Network(_) => true,
267            _ => false,
268        }
269    }
270
271    /// Get retry-after duration in seconds if available.
272    ///
273    /// Only returns a value for [`Error::RateLimit`] errors that include
274    /// a Retry-After header from the API.
275    ///
276    /// # Example
277    ///
278    /// ```rust
279    /// use claude_sdk::Error;
280    /// use std::time::Duration;
281    ///
282    /// async fn wait_and_retry(err: &Error) {
283    ///     if let Some(seconds) = err.retry_after() {
284    ///         tokio::time::sleep(Duration::from_secs(seconds)).await;
285    ///     }
286    /// }
287    /// ```
288    pub fn retry_after(&self) -> Option<u64> {
289        match self {
290            Error::RateLimit { retry_after, .. } => *retry_after,
291            _ => None,
292        }
293    }
294}
295
296/// API error response structure
297#[derive(Debug, serde::Deserialize)]
298pub struct ApiErrorResponse {
299    #[serde(rename = "type")]
300    pub error_type: String,
301    pub error: ApiErrorDetail,
302}
303
304#[derive(Debug, serde::Deserialize)]
305pub struct ApiErrorDetail {
306    #[serde(rename = "type")]
307    pub error_type: String,
308    pub message: String,
309}