Skip to main content

openrouter_rs/
error.rs

1//! # Error Handling
2//!
3//! This module provides comprehensive error types for the OpenRouter SDK.
4//! All errors implement the standard library's `Error` trait and can be
5//! used with any error handling framework.
6//!
7//! ## 🎯 Error Categories
8//!
9//! ### HTTP Request Errors
10//! - **`HttpRequest`**: Network-level failures (connectivity, timeouts)
11//! - **`Api`**: Normalized API failures from the OpenRouter API
12//!
13//! ### OpenRouter API Errors
14//! - **`ApiErrorContext`**: status/code/message/request-id/metadata envelope
15//! - **`ApiErrorKind::Moderation`**: Content moderation violations
16//! - **`ApiErrorKind::Provider`**: Provider-specific upstream failures
17//!
18//! ### Validation Errors
19//! - **`ConfigError`**: Invalid SDK configuration or request/tool validation issues
20//! - **`KeyNotConfigured`**: Missing or invalid API keys
21//!
22//! ### Data Processing Errors
23//! - **`UninitializedFieldError`**: Builder pattern validation failures
24//! - **`Serialization`**: JSON serialization/deserialization errors
25//!
26//! ### System Errors
27//! - **`Io`**: File system and I/O operations
28//! - **`Unknown`**: Unexpected errors
29//!
30//! ## 🚀 Usage Examples
31//!
32//! ### Basic Error Handling
33//!
34//! ```no_run
35//! use openrouter_rs::{OpenRouterClient, error::OpenRouterError};
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! let client = OpenRouterClient::builder()
39//!     .api_key("invalid_key")
40//!     .build()?;
41//!
42//! match client.models().list().await {
43//!     Ok(models) => println!("Found {} models", models.len()),
44//!     Err(OpenRouterError::Api(api_error)) => {
45//!         eprintln!("API error {}: {}", api_error.status, api_error.message);
46//!     }
47//!     Err(OpenRouterError::HttpRequest(e)) => {
48//!         eprintln!("Network error: {}", e);
49//!     }
50//!     Err(e) => eprintln!("Other error: {}", e),
51//! }
52//! # Ok(())
53//! # }
54//! ```
55//!
56//! ### Rate Limiting Handling
57//!
58//! ```no_run
59//! use openrouter_rs::error::OpenRouterError;
60//! use openrouter_rs::{OpenRouterClient, api::chat::{ChatCompletionRequest, Message}, types::Role};
61//!
62//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
63//! let client = OpenRouterClient::builder()
64//!     .api_key("your_api_key")
65//!     .build()?;
66//! let request = ChatCompletionRequest::builder()
67//!     .model("google/gemini-2.5-flash")
68//!     .messages(vec![Message::new(Role::User, "Hello!")])
69//!     .build()?;
70//!
71//! match client.chat().create(&request).await {
72//!     Err(OpenRouterError::Api(api_error)) if api_error.is_retryable() => {
73//!         println!("Rate limited, retrying after delay...");
74//!         tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
75//!         // Retry logic here
76//!     }
77//!     Ok(response) => println!("Success!"),
78//!     Err(e) => return Err(e.into()),
79//! }
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! ### Validation Error Handling
85//!
86//! ```rust
87//! use openrouter_rs::error::OpenRouterError;
88//!
89//! fn describe(error: OpenRouterError) {
90//!     match error {
91//!         OpenRouterError::ConfigError(msg) => {
92//!             eprintln!("Invalid SDK configuration: {}", msg);
93//!         }
94//!         other => eprintln!("Unexpected error: {}", other),
95//!     }
96//! }
97//! ```
98//!
99//! ## 🔄 Error Conversion
100//!
101//! The SDK automatically converts common error types:
102//!
103//! - transport client errors → `OpenRouterError::HttpRequest`
104//! - `serde_json::Error` → `OpenRouterError::Serialization`
105//! - `std::io::Error` → `OpenRouterError::Io`
106//! - `derive_builder::UninitializedFieldError` → `OpenRouterError::UninitializedFieldError`
107
108use http::StatusCode;
109use serde_json::Value;
110use thiserror::Error;
111
112/// Stable, backend-neutral request error wrapper used by [`OpenRouterError::HttpRequest`].
113#[derive(Debug, Clone, PartialEq, Eq, Error)]
114#[error("{message}")]
115pub struct HttpRequestError {
116    message: String,
117}
118
119impl HttpRequestError {
120    pub fn new(message: impl Into<String>) -> Self {
121        Self {
122            message: message.into(),
123        }
124    }
125
126    pub fn message(&self) -> &str {
127        &self.message
128    }
129}
130
131/// Normalized API error category.
132#[derive(Debug, Clone)]
133pub enum ApiErrorKind {
134    /// Generic API error payload without specialized metadata.
135    Generic,
136    /// Moderation rejection with structured moderation metadata.
137    Moderation {
138        reasons: Vec<String>,
139        flagged_input: String,
140        provider_name: String,
141        model_slug: String,
142    },
143    /// Provider-side failure metadata.
144    Provider { provider_name: String, raw: Value },
145}
146
147/// Normalized API error payload used across all endpoint modules.
148#[derive(Debug, Clone)]
149pub struct ApiErrorContext {
150    pub status: StatusCode,
151    pub api_code: Option<i64>,
152    pub message: String,
153    pub request_id: Option<String>,
154    pub metadata: Option<Value>,
155    pub kind: ApiErrorKind,
156}
157
158impl ApiErrorContext {
159    /// Returns true if the request is typically retryable.
160    pub fn is_retryable(&self) -> bool {
161        matches!(
162            self.status,
163            StatusCode::REQUEST_TIMEOUT
164                | StatusCode::TOO_MANY_REQUESTS
165                | StatusCode::INTERNAL_SERVER_ERROR
166                | StatusCode::BAD_GATEWAY
167                | StatusCode::SERVICE_UNAVAILABLE
168                | StatusCode::GATEWAY_TIMEOUT
169        )
170    }
171
172    pub fn is_client_error(&self) -> bool {
173        self.status.is_client_error()
174    }
175
176    pub fn is_server_error(&self) -> bool {
177        self.status.is_server_error()
178    }
179}
180
181impl std::fmt::Display for ApiErrorContext {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        if let Some(api_code) = self.api_code {
184            write!(
185                f,
186                "API error {} (api_code={}): {}",
187                self.status, api_code, self.message
188            )?;
189        } else {
190            write!(f, "API error {}: {}", self.status, self.message)?;
191        }
192
193        if let Some(request_id) = &self.request_id {
194            write!(f, " [request_id={}]", request_id)?;
195        }
196
197        Ok(())
198    }
199}
200
201/// Comprehensive error type for OpenRouter SDK operations
202///
203/// This enum covers all possible error conditions that can occur when
204/// using the OpenRouter SDK, from network issues to API-specific errors.
205/// All variants implement `std::error::Error` and provide detailed
206/// context information.
207///
208/// # Examples
209///
210/// ```rust
211/// use openrouter_rs::error::{ApiErrorKind, OpenRouterError};
212///
213/// // Pattern matching on specific error types
214/// fn handle_error(error: OpenRouterError) {
215///     match error {
216///         OpenRouterError::Api(api_error) if api_error.status == http::StatusCode::UNAUTHORIZED => {
217///             println!("Check your API key");
218///         }
219///         OpenRouterError::Api(api_error) => match &api_error.kind {
220///             ApiErrorKind::Moderation { reasons, .. } => {
221///                 println!("Content flagged for: {:?}", reasons);
222///             }
223///             _ => println!("Other API error: {}", api_error),
224///         },
225///         OpenRouterError::ConfigError(msg) => {
226///             println!("Configuration issue: {}", msg);
227///         }
228///         _ => println!("Other error: {}", error),
229///     }
230/// }
231/// ```
232#[derive(Error, Debug)]
233pub enum OpenRouterError {
234    // HTTP request errors
235    #[error("HTTP request failed: {0}")]
236    HttpRequest(HttpRequestError),
237
238    // API response errors
239    #[error("{0}")]
240    Api(Box<ApiErrorContext>),
241
242    // Configuration errors
243    #[error("Config error: {0}")]
244    ConfigError(String),
245
246    #[error("API key not configured")]
247    KeyNotConfigured,
248
249    // Data processing errors
250    #[error("Uninitialized field error: {0}")]
251    UninitializedFieldError(#[from] derive_builder::UninitializedFieldError),
252
253    #[error("Serialization error: {0}")]
254    Serialization(#[from] serde_json::Error),
255
256    // System IO errors
257    #[error("IO error: {0}")]
258    Io(#[from] std::io::Error),
259
260    // Uncategorized errors
261    #[error("Unknown error: {0}")]
262    Unknown(String),
263}
264
265impl From<reqwest::Error> for OpenRouterError {
266    fn from(err: reqwest::Error) -> Self {
267        OpenRouterError::HttpRequest(HttpRequestError::new(err.to_string()))
268    }
269}