1pub type Result<T> = std::result::Result<T, GatewayError>;
8
9#[derive(Debug, thiserror::Error)]
11pub enum GatewayError {
12 #[error("Rate limit exceeded: {message}")]
14 RateLimitExceeded {
15 message: String,
17 retry_after: Option<u64>,
19 },
20
21 #[error("Authentication failed: {0}")]
23 AuthenticationFailed(String),
24
25 #[error("Authorization failed: {0}")]
27 AuthorizationFailed(String),
28
29 #[error("Invalid API key")]
31 InvalidApiKey,
32
33 #[error("Token expired")]
35 TokenExpired,
36
37 #[error("Invalid token: {0}")]
39 InvalidToken(String),
40
41 #[error("JWT error: {0}")]
43 JwtError(#[from] jsonwebtoken::errors::Error),
44
45 #[error("OAuth2 error: {0}")]
47 OAuth2Error(String),
48
49 #[error("GraphQL error: {0}")]
51 GraphQLError(String),
52
53 #[error("WebSocket error: {0}")]
55 WebSocketError(String),
56
57 #[error("Invalid request: {0}")]
59 InvalidRequest(String),
60
61 #[error("Unsupported API version: {version}")]
63 UnsupportedVersion {
64 version: String,
66 supported: Vec<String>,
68 },
69
70 #[error("Transformation error: {0}")]
72 TransformationError(String),
73
74 #[error("Schema validation error: {0}")]
76 SchemaValidationError(String),
77
78 #[error("Load balancer error: {0}")]
80 LoadBalancerError(String),
81
82 #[error("Backend unavailable: {0}")]
84 BackendUnavailable(String),
85
86 #[error("Circuit breaker open for backend: {0}")]
88 CircuitBreakerOpen(String),
89
90 #[error("Operation timed out: {0}")]
92 Timeout(String),
93
94 #[cfg(feature = "redis")]
96 #[error("Redis error: {0}")]
97 RedisError(#[from] redis::RedisError),
98
99 #[error("Serialization error: {0}")]
101 SerializationError(#[from] serde_json::Error),
102
103 #[error("HTTP error: {0}")]
105 HttpError(String),
106
107 #[error("Internal server error: {0}")]
109 InternalError(String),
110
111 #[error("Configuration error: {0}")]
113 ConfigError(String),
114}
115
116impl GatewayError {
117 pub fn status_code(&self) -> http::StatusCode {
119 match self {
120 Self::RateLimitExceeded { .. } => http::StatusCode::TOO_MANY_REQUESTS,
121 Self::AuthenticationFailed(_) | Self::InvalidApiKey | Self::InvalidToken(_) => {
122 http::StatusCode::UNAUTHORIZED
123 }
124 Self::AuthorizationFailed(_) => http::StatusCode::FORBIDDEN,
125 Self::TokenExpired => http::StatusCode::UNAUTHORIZED,
126 Self::InvalidRequest(_) | Self::SchemaValidationError(_) => {
127 http::StatusCode::BAD_REQUEST
128 }
129 Self::UnsupportedVersion { .. } => http::StatusCode::NOT_ACCEPTABLE,
130 Self::BackendUnavailable(_) | Self::CircuitBreakerOpen(_) => {
131 http::StatusCode::SERVICE_UNAVAILABLE
132 }
133 Self::Timeout(_) => http::StatusCode::GATEWAY_TIMEOUT,
134 _ => http::StatusCode::INTERNAL_SERVER_ERROR,
135 }
136 }
137
138 pub fn is_retryable(&self) -> bool {
140 matches!(
141 self,
142 Self::RateLimitExceeded { .. }
143 | Self::BackendUnavailable(_)
144 | Self::CircuitBreakerOpen(_)
145 | Self::Timeout(_)
146 | Self::LoadBalancerError(_)
147 )
148 }
149
150 pub fn retry_after(&self) -> Option<u64> {
152 match self {
153 Self::RateLimitExceeded { retry_after, .. } => *retry_after,
154 Self::BackendUnavailable(_) => Some(5),
155 Self::CircuitBreakerOpen(_) => Some(30),
156 _ => None,
157 }
158 }
159
160 pub fn to_json_response(&self) -> serde_json::Value {
162 serde_json::json!({
163 "error": {
164 "code": self.error_code(),
165 "message": self.to_string(),
166 "status": self.status_code().as_u16(),
167 "retryable": self.is_retryable(),
168 "retry_after": self.retry_after(),
169 }
170 })
171 }
172
173 pub fn error_code(&self) -> &str {
175 match self {
176 Self::RateLimitExceeded { .. } => "RATE_LIMIT_EXCEEDED",
177 Self::AuthenticationFailed(_) => "AUTHENTICATION_FAILED",
178 Self::AuthorizationFailed(_) => "AUTHORIZATION_FAILED",
179 Self::InvalidApiKey => "INVALID_API_KEY",
180 Self::TokenExpired => "TOKEN_EXPIRED",
181 Self::InvalidToken(_) => "INVALID_TOKEN",
182 Self::JwtError(_) => "JWT_ERROR",
183 Self::OAuth2Error(_) => "OAUTH2_ERROR",
184 Self::GraphQLError(_) => "GRAPHQL_ERROR",
185 Self::WebSocketError(_) => "WEBSOCKET_ERROR",
186 Self::InvalidRequest(_) => "INVALID_REQUEST",
187 Self::UnsupportedVersion { .. } => "UNSUPPORTED_VERSION",
188 Self::TransformationError(_) => "TRANSFORMATION_ERROR",
189 Self::SchemaValidationError(_) => "SCHEMA_VALIDATION_ERROR",
190 Self::LoadBalancerError(_) => "LOAD_BALANCER_ERROR",
191 Self::BackendUnavailable(_) => "BACKEND_UNAVAILABLE",
192 Self::CircuitBreakerOpen(_) => "CIRCUIT_BREAKER_OPEN",
193 Self::Timeout(_) => "TIMEOUT",
194 #[cfg(feature = "redis")]
195 Self::RedisError(_) => "REDIS_ERROR",
196 Self::SerializationError(_) => "SERIALIZATION_ERROR",
197 Self::HttpError(_) => "HTTP_ERROR",
198 Self::InternalError(_) => "INTERNAL_ERROR",
199 Self::ConfigError(_) => "CONFIG_ERROR",
200 }
201 }
202}
203
204impl From<GatewayError> for http::StatusCode {
205 fn from(error: GatewayError) -> Self {
206 error.status_code()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_error_status_codes() {
216 assert_eq!(
217 GatewayError::RateLimitExceeded {
218 message: "test".to_string(),
219 retry_after: Some(60)
220 }
221 .status_code(),
222 http::StatusCode::TOO_MANY_REQUESTS
223 );
224
225 assert_eq!(
226 GatewayError::AuthenticationFailed("test".to_string()).status_code(),
227 http::StatusCode::UNAUTHORIZED
228 );
229
230 assert_eq!(
231 GatewayError::AuthorizationFailed("test".to_string()).status_code(),
232 http::StatusCode::FORBIDDEN
233 );
234 }
235
236 #[test]
237 fn test_error_retryable() {
238 assert!(
239 GatewayError::RateLimitExceeded {
240 message: "test".to_string(),
241 retry_after: Some(60)
242 }
243 .is_retryable()
244 );
245
246 assert!(GatewayError::BackendUnavailable("test".to_string()).is_retryable());
247
248 assert!(!GatewayError::InvalidApiKey.is_retryable());
249 }
250
251 #[test]
252 fn test_retry_after() {
253 let error = GatewayError::RateLimitExceeded {
254 message: "test".to_string(),
255 retry_after: Some(60),
256 };
257 assert_eq!(error.retry_after(), Some(60));
258
259 assert_eq!(
260 GatewayError::BackendUnavailable("test".to_string()).retry_after(),
261 Some(5)
262 );
263 }
264
265 #[test]
266 fn test_json_response() {
267 let error = GatewayError::InvalidApiKey;
268 let json = error.to_json_response();
269
270 assert_eq!(json["error"]["code"], "INVALID_API_KEY");
271 assert_eq!(json["error"]["status"], 401);
272 assert_eq!(json["error"]["retryable"], false);
273 }
274}