openapi_to_rust/http_error.rs
1//! HTTP client error types with comprehensive retry detection.
2//!
3//! This module provides error types for HTTP operations with built-in support for
4//! retry detection, error categorization, and detailed error information.
5//!
6//! # Overview
7//!
8//! The [`HttpError`] enum covers all possible failure modes for HTTP requests:
9//! - Network errors (connection failures, DNS issues)
10//! - HTTP errors (4xx client errors, 5xx server errors)
11//! - Serialization/deserialization errors
12//! - Authentication errors
13//! - Timeouts
14//! - Configuration errors
15//!
16//! # Retry Detection
17//!
18//! The error type includes built-in retry detection via [`HttpError::is_retryable()`]:
19//! - Network errors → retryable
20//! - Timeouts → retryable
21//! - 429 (rate limit) → retryable
22//! - 500, 502, 503, 504 (server errors) → retryable
23//! - All other errors → not retryable
24//!
25//! When using the generated HTTP client with retry middleware (reqwest-retry),
26//! retryable errors are automatically retried with exponential backoff.
27//!
28//! # Examples
29//!
30//! ## Basic Error Handling
31//!
32//! ```
33//! # use openapi_to_rust::http_error::HttpError;
34//! fn handle_api_error(error: HttpError) {
35//! match error {
36//! HttpError::Network(e) => {
37//! eprintln!("Network error: {}", e);
38//! // Will be retried automatically if retry is configured
39//! }
40//! HttpError::Http { status, message, .. } => {
41//! match status {
42//! 400 => eprintln!("Bad request: {}", message),
43//! 401 => eprintln!("Unauthorized - check API key"),
44//! 404 => eprintln!("Not found"),
45//! 429 => eprintln!("Rate limited - will retry"),
46//! 500..=599 => eprintln!("Server error: {}", message),
47//! _ => eprintln!("HTTP error {}: {}", status, message),
48//! }
49//! }
50//! HttpError::Timeout => {
51//! eprintln!("Request timeout - will retry");
52//! }
53//! e => {
54//! eprintln!("Other error: {}", e);
55//! }
56//! }
57//! }
58//! ```
59//!
60//! ## Retry Detection
61//!
62//! ```
63//! # use openapi_to_rust::http_error::HttpError;
64//! fn classify_error(error: &HttpError) {
65//! if error.is_retryable() {
66//! println!("Retryable error: {}", error);
67//! // If retry middleware is configured, this will be retried automatically
68//! } else if error.is_client_error() {
69//! println!("Client error (4xx): fix the request");
70//! } else if error.is_server_error() {
71//! println!("Server error (5xx): may be transient");
72//! } else {
73//! println!("Non-retryable error: {}", error);
74//! }
75//! }
76//! ```
77//!
78//! ## Creating Errors
79//!
80//! ```
81//! use openapi_to_rust::http_error::HttpError;
82//!
83//! // Create HTTP error from status code
84//! let error = HttpError::from_status(404, "Resource not found", None);
85//!
86//! // Create serialization error
87//! let error = HttpError::serialization_error("invalid JSON");
88//!
89//! // Create deserialization error
90//! let error = HttpError::deserialization_error("unexpected field");
91//! ```
92//!
93//! # Integration with reqwest-retry
94//!
95//! When the generated HTTP client is configured with retry middleware,
96//! the retry logic automatically handles retryable errors:
97//!
98//! ```toml
99//! [http_client.retry]
100//! max_retries = 3
101//! initial_delay_ms = 500
102//! max_delay_ms = 16000
103//! ```
104//!
105//! The retry middleware uses exponential backoff and will retry:
106//! - Network errors (connection failures)
107//! - Timeouts
108//! - HTTP 429 (rate limit)
109//! - HTTP 500, 502, 503, 504 (server errors)
110//!
111//! # Error Categories
112//!
113//! Errors can be categorized using helper methods:
114//! - [`HttpError::is_retryable()`] - Should this error be retried?
115//! - [`HttpError::is_client_error()`] - Is this a 4xx error?
116//! - [`HttpError::is_server_error()`] - Is this a 5xx error?
117
118use thiserror::Error;
119
120/// HTTP client errors that can occur during API requests
121#[derive(Error, Debug)]
122pub enum HttpError {
123 /// Network or connection error
124 #[error("Network error: {0}")]
125 Network(#[from] reqwest::Error),
126
127 /// Request serialization error
128 #[error("Failed to serialize request: {0}")]
129 Serialization(String),
130
131 /// Response deserialization error
132 #[error("Failed to deserialize response: {0}")]
133 Deserialization(String),
134
135 /// HTTP error response (4xx, 5xx)
136 #[error("HTTP error {status}: {message}")]
137 Http {
138 status: u16,
139 message: String,
140 body: Option<String>,
141 },
142
143 /// Authentication error
144 #[error("Authentication error: {0}")]
145 Auth(String),
146
147 /// Request timeout
148 #[error("Request timeout")]
149 Timeout,
150
151 /// Invalid configuration
152 #[error("Configuration error: {0}")]
153 Config(String),
154
155 /// Generic error
156 #[error("{0}")]
157 Other(String),
158}
159
160impl HttpError {
161 /// Create an HTTP error from a status code and message
162 pub fn from_status(status: u16, message: impl Into<String>, body: Option<String>) -> Self {
163 Self::Http {
164 status,
165 message: message.into(),
166 body,
167 }
168 }
169
170 /// Create a serialization error
171 pub fn serialization_error(error: impl std::fmt::Display) -> Self {
172 Self::Serialization(error.to_string())
173 }
174
175 /// Create a deserialization error
176 pub fn deserialization_error(error: impl std::fmt::Display) -> Self {
177 Self::Deserialization(error.to_string())
178 }
179
180 /// Check if this is a client error (4xx)
181 pub fn is_client_error(&self) -> bool {
182 matches!(self, Self::Http { status, .. } if *status >= 400 && *status < 500)
183 }
184
185 /// Check if this is a server error (5xx)
186 pub fn is_server_error(&self) -> bool {
187 matches!(self, Self::Http { status, .. } if *status >= 500 && *status < 600)
188 }
189
190 /// Check if this error is retryable
191 pub fn is_retryable(&self) -> bool {
192 match self {
193 Self::Network(_) => true,
194 Self::Timeout => true,
195 Self::Http { status, .. } => {
196 // Retry on 429 (rate limit), 500, 502, 503, 504
197 matches!(status, 429 | 500 | 502 | 503 | 504)
198 }
199 _ => false,
200 }
201 }
202}
203
204/// Result type for HTTP operations
205pub type HttpResult<T> = Result<T, HttpError>;