Skip to main content

litellm_rs/server/routes/
mod.rs

1//! HTTP route modules
2//!
3//! This module contains all HTTP route handlers organized by functionality.
4
5pub mod admin;
6pub mod admin_dashboard;
7mod admin_openapi;
8mod admin_providers;
9mod admin_request_ledger;
10mod admin_routing;
11mod admin_routing_policy;
12pub mod ai;
13pub mod auth;
14pub mod budget;
15pub mod health;
16pub mod keys;
17#[cfg(feature = "mcp")]
18pub mod mcp;
19pub mod pricing;
20pub mod teams;
21
22use actix_web::HttpResponse;
23
24/// Standard API response structure
25#[derive(Debug, Clone, serde::Serialize)]
26pub struct ApiResponse<T> {
27    /// Whether the request was successful
28    pub success: bool,
29    /// Response data (if successful)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub data: Option<T>,
32    /// Error message (if failed)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub error: Option<String>,
35    /// Additional metadata
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub meta: Option<serde_json::Value>,
38}
39
40impl<T> ApiResponse<T>
41where
42    T: serde::Serialize,
43{
44    /// Create a successful response
45    pub fn success(data: T) -> Self {
46        Self {
47            success: true,
48            data: Some(data),
49            error: None,
50            meta: None,
51        }
52    }
53
54    /// Create an error response
55    pub fn error(message: String) -> ApiResponse<()> {
56        ApiResponse {
57            success: false,
58            data: None,
59            error: Some(message),
60            meta: None,
61        }
62    }
63}
64
65impl<T> ApiResponse<T> {
66    /// Create an error response for any type
67    pub fn error_for_type(message: String) -> ApiResponse<T> {
68        ApiResponse {
69            success: false,
70            data: None,
71            error: Some(message),
72            meta: None,
73        }
74    }
75}
76
77impl<T> ApiResponse<T>
78where
79    T: serde::Serialize,
80{
81    /// Convert the API response to an HTTP response.
82    #[deprecated(note = "Use explicit route status helpers instead")]
83    pub fn to_http_response(&self) -> HttpResponse {
84        if self.success {
85            HttpResponse::Ok().json(self)
86        } else {
87            HttpResponse::BadRequest().json(self)
88        }
89    }
90}
91
92/// Pagination metadata
93#[derive(Debug, Clone, serde::Serialize)]
94pub struct PaginationMeta {
95    /// Current page number
96    pub page: u32,
97    /// Number of items per page
98    pub limit: u32,
99    /// Total number of items
100    pub total: u64,
101    /// Total number of pages
102    pub pages: u32,
103    /// Whether there is a next page
104    pub has_next: bool,
105    /// Whether there is a previous page
106    pub has_prev: bool,
107}
108
109impl PaginationMeta {
110    /// Create pagination metadata
111    pub fn new(page: u32, limit: u32, total: u64) -> Self {
112        let pages = ((total as f64) / (limit as f64)).ceil() as u32;
113
114        Self {
115            page,
116            limit,
117            total,
118            pages,
119            has_next: page < pages,
120            has_prev: page > 1,
121        }
122    }
123}
124
125/// Paginated response
126#[derive(Debug, Clone, serde::Serialize)]
127pub struct PaginatedResponse<T> {
128    /// Response items
129    pub items: Vec<T>,
130    /// Pagination metadata
131    pub pagination: PaginationMeta,
132}
133
134impl<T> PaginatedResponse<T>
135where
136    T: serde::Serialize,
137{
138    /// Create a paginated response
139    pub fn new(items: Vec<T>, page: u32, limit: u32, total: u64) -> Self {
140        Self {
141            items,
142            pagination: PaginationMeta::new(page, limit, total),
143        }
144    }
145}
146
147/// Query parameters for pagination
148#[derive(Debug, Clone, serde::Deserialize)]
149pub struct PaginationQuery {
150    /// Page number (1-based)
151    #[serde(default = "default_page")]
152    pub page: u32,
153    /// Number of items per page
154    #[serde(default = "default_limit")]
155    pub limit: u32,
156}
157
158fn default_page() -> u32 {
159    1
160}
161
162fn default_limit() -> u32 {
163    20
164}
165
166impl PaginationQuery {
167    /// Validate pagination parameters
168    pub fn validate(&self) -> Result<(), String> {
169        if self.page == 0 {
170            return Err("Page must be greater than 0".to_string());
171        }
172        if self.limit == 0 {
173            return Err("Limit must be greater than 0".to_string());
174        }
175        if self.limit > 1000 {
176            return Err("Limit cannot exceed 1000".to_string());
177        }
178        Ok(())
179    }
180
181    /// Get offset for database queries
182    pub fn offset(&self) -> u32 {
183        (self.page - 1) * self.limit
184    }
185}
186
187/// Query parameters for sorting
188#[derive(Debug, Clone, serde::Deserialize)]
189pub struct SortQuery {
190    /// Field to sort by
191    #[serde(default = "default_sort_by")]
192    pub sort_by: String,
193    /// Sort order (asc or desc)
194    #[serde(default = "default_sort_order")]
195    pub sort_order: String,
196}
197
198fn default_sort_by() -> String {
199    "created_at".to_string()
200}
201
202fn default_sort_order() -> String {
203    "desc".to_string()
204}
205
206impl SortQuery {
207    /// Validate sort parameters
208    pub fn validate(&self, valid_fields: &[&str]) -> Result<(), String> {
209        if !valid_fields.contains(&self.sort_by.as_str()) {
210            return Err(format!("Invalid sort field: {}", self.sort_by));
211        }
212        if !["asc", "desc"].contains(&self.sort_order.as_str()) {
213            return Err("Sort order must be 'asc' or 'desc'".to_string());
214        }
215        Ok(())
216    }
217}
218
219/// Combined query parameters for listing endpoints
220#[derive(Debug, Clone, serde::Deserialize)]
221pub struct ListQuery {
222    /// Pagination parameters (page, limit)
223    #[serde(flatten)]
224    pub pagination: PaginationQuery,
225    /// Sorting parameters (sort_by, sort_order)
226    #[serde(flatten)]
227    pub sort: SortQuery,
228}
229
230impl ListQuery {
231    /// Validate all query parameters
232    pub fn validate(&self, valid_sort_fields: &[&str]) -> Result<(), String> {
233        self.pagination.validate()?;
234        self.sort.validate(valid_sort_fields)?;
235        Ok(())
236    }
237}
238
239/// Error response helpers
240pub mod errors {
241    use super::*;
242    use crate::utils::error::gateway_error::GatewayError;
243    use actix_web::ResponseError;
244
245    /// Convert GatewayError to HTTP response.
246    ///
247    /// Kept as a compatibility shim while routes migrate to `ResponseError` directly.
248    pub fn gateway_error_to_response(error: GatewayError) -> HttpResponse {
249        error.error_response()
250    }
251
252    /// Create a validation error response
253    pub fn validation_error(message: &str) -> HttpResponse {
254        HttpResponse::BadRequest().json(ApiResponse::<()>::error(message.to_string()))
255    }
256
257    /// Create an unauthorized error response
258    pub fn unauthorized_error(message: &str) -> HttpResponse {
259        HttpResponse::Unauthorized().json(ApiResponse::<()>::error(message.to_string()))
260    }
261
262    /// Create a forbidden error response
263    pub fn forbidden_error(message: &str) -> HttpResponse {
264        HttpResponse::Forbidden().json(ApiResponse::<()>::error(message.to_string()))
265    }
266
267    /// Create a not found error response
268    pub fn not_found_error(message: &str) -> HttpResponse {
269        HttpResponse::NotFound().json(ApiResponse::<()>::error(message.to_string()))
270    }
271
272    /// Create an internal server error response
273    pub fn internal_error(message: &str) -> HttpResponse {
274        HttpResponse::InternalServerError().json(ApiResponse::<()>::error(message.to_string()))
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_api_response_success() {
284        let response = ApiResponse::success("test data");
285        assert!(response.success);
286        assert_eq!(response.data, Some("test data"));
287        assert!(response.error.is_none());
288    }
289
290    #[test]
291    fn test_api_response_error() {
292        let response = ApiResponse::<()>::error("test error".to_string());
293        assert!(!response.success);
294        assert!(response.data.is_none());
295        assert_eq!(response.error, Some("test error".to_string()));
296    }
297
298    #[test]
299    #[allow(deprecated)]
300    fn test_api_response_to_http_response_remains_compatibility_shim() {
301        let success = ApiResponse::success("test data").to_http_response();
302        assert_eq!(success.status(), actix_web::http::StatusCode::OK);
303
304        let error = ApiResponse::<()>::error("test error".to_string()).to_http_response();
305        assert_eq!(error.status(), actix_web::http::StatusCode::BAD_REQUEST);
306    }
307
308    #[actix_web::test]
309    async fn test_gateway_error_helpers_keep_api_response_envelope() {
310        let response = errors::validation_error("test validation error");
311
312        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
313        let body = actix_web::body::to_bytes(response.into_body())
314            .await
315            .unwrap();
316        let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
317
318        assert_eq!(json["success"], false);
319        assert_eq!(json["error"], "test validation error");
320        assert!(json["data"].is_null());
321    }
322
323    #[test]
324    fn test_error_helpers_preserve_specific_http_statuses() {
325        assert_eq!(
326            errors::unauthorized_error("unauthorized").status(),
327            actix_web::http::StatusCode::UNAUTHORIZED
328        );
329        assert_eq!(
330            errors::forbidden_error("forbidden").status(),
331            actix_web::http::StatusCode::FORBIDDEN
332        );
333        assert_eq!(
334            errors::not_found_error("missing").status(),
335            actix_web::http::StatusCode::NOT_FOUND
336        );
337        assert_eq!(
338            errors::internal_error("internal").status(),
339            actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
340        );
341    }
342
343    #[test]
344    fn test_pagination_meta() {
345        let meta = PaginationMeta::new(2, 10, 25);
346        assert_eq!(meta.page, 2);
347        assert_eq!(meta.limit, 10);
348        assert_eq!(meta.total, 25);
349        assert_eq!(meta.pages, 3);
350        assert!(meta.has_next);
351        assert!(meta.has_prev);
352    }
353
354    #[test]
355    fn test_pagination_query_validation() {
356        let valid_query = PaginationQuery { page: 1, limit: 20 };
357        assert!(valid_query.validate().is_ok());
358
359        let invalid_page = PaginationQuery { page: 0, limit: 20 };
360        assert!(invalid_page.validate().is_err());
361
362        let invalid_limit = PaginationQuery { page: 1, limit: 0 };
363        assert!(invalid_limit.validate().is_err());
364
365        let too_large_limit = PaginationQuery {
366            page: 1,
367            limit: 2000,
368        };
369        assert!(too_large_limit.validate().is_err());
370    }
371
372    #[test]
373    fn test_pagination_query_offset() {
374        let query = PaginationQuery { page: 3, limit: 10 };
375        assert_eq!(query.offset(), 20);
376    }
377
378    #[test]
379    fn test_sort_query_validation() {
380        let valid_fields = &["name", "created_at", "updated_at"];
381
382        let valid_query = SortQuery {
383            sort_by: "name".to_string(),
384            sort_order: "asc".to_string(),
385        };
386        assert!(valid_query.validate(valid_fields).is_ok());
387
388        let invalid_field = SortQuery {
389            sort_by: "invalid".to_string(),
390            sort_order: "asc".to_string(),
391        };
392        assert!(invalid_field.validate(valid_fields).is_err());
393
394        let invalid_order = SortQuery {
395            sort_by: "name".to_string(),
396            sort_order: "invalid".to_string(),
397        };
398        assert!(invalid_order.validate(valid_fields).is_err());
399    }
400}
401
402#[cfg(feature = "a2a")]
403pub mod a2a;