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}