Skip to main content

rustapi_core/
handler.rs

1//! Handler trait and utilities
2//!
3//! This module provides the [`Handler`] trait and related types for defining
4//! HTTP request handlers in RustAPI.
5//!
6//! # Handler Functions
7//!
8//! Any async function that takes extractors as parameters and returns a type
9//! implementing [`IntoResponse`] can be used as a handler:
10//!
11//! ```rust,ignore
12//! use rustapi_core::{Json, Path, IntoResponse};
13//! use serde::{Deserialize, Serialize};
14//!
15//! // No parameters
16//! async fn hello() -> &'static str {
17//!     "Hello, World!"
18//! }
19//!
20//! // With extractors
21//! async fn get_user(Path(id): Path<i64>) -> Json<User> {
22//!     Json(User { id, name: "Alice".to_string() })
23//! }
24//!
25//! // Multiple extractors (up to 5 supported)
26//! async fn create_user(
27//!     State(db): State<DbPool>,
28//!     Json(body): Json<CreateUser>,
29//! ) -> Result<Created<User>, ApiError> {
30//!     // ...
31//! }
32//! ```
33//!
34//! # Route Helpers
35//!
36//! The module provides helper functions for creating routes with metadata:
37//!
38//! ```rust,ignore
39//! use rustapi_core::handler::{get_route, post_route};
40//!
41//! let get = get_route("/users", list_users);
42//! let post = post_route("/users", create_user);
43//! ```
44//!
45//! # Macro-Based Routing
46//!
47//! For more ergonomic routing, use the `#[rustapi::get]`, `#[rustapi::post]`, etc.
48//! macros from `rustapi-macros`:
49//!
50//! ```rust,ignore
51//! #[rustapi::get("/users/{id}")]
52//! async fn get_user(Path(id): Path<i64>) -> Json<User> {
53//!     // ...
54//! }
55//! ```
56
57use crate::extract::FromRequest;
58use crate::request::Request;
59use crate::response::{IntoResponse, Response};
60use rustapi_openapi::{Operation, OperationModifier, ResponseModifier};
61use std::future::Future;
62use std::marker::PhantomData;
63use std::pin::Pin;
64
65mod sealed {
66    pub trait Sealed {}
67
68    impl<T> Sealed for T where T: Clone + Send + Sync + Sized + 'static {}
69}
70
71/// Trait representing an async handler function
72pub trait Handler<T>: sealed::Sealed + Clone + Send + Sync + Sized + 'static {
73    /// The response type
74    type Future: Future<Output = Response> + Send + 'static;
75
76    /// Call the handler with the request
77    fn call(self, req: Request) -> Self::Future;
78
79    /// Update the OpenAPI operation
80    fn update_operation(op: &mut Operation);
81
82    /// Register any OpenAPI components referenced by this handler.
83    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec);
84}
85
86/// Wrapper to convert a Handler into a tower Service
87pub struct HandlerService<H, T> {
88    handler: H,
89    _marker: PhantomData<fn() -> T>,
90}
91
92impl<H, T> HandlerService<H, T> {
93    pub fn new(handler: H) -> Self {
94        Self {
95            handler,
96            _marker: PhantomData,
97        }
98    }
99}
100
101impl<H: Clone, T> Clone for HandlerService<H, T> {
102    fn clone(&self) -> Self {
103        Self {
104            handler: self.handler.clone(),
105            _marker: PhantomData,
106        }
107    }
108}
109
110// Implement Handler for async functions with 0-6 extractors
111
112// 0 args
113impl<F, Fut, Res> Handler<()> for F
114where
115    F: FnOnce() -> Fut + Clone + Send + Sync + 'static,
116    Fut: Future<Output = Res> + Send + 'static,
117    Res: IntoResponse + ResponseModifier,
118{
119    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
120
121    fn call(self, _req: Request) -> Self::Future {
122        Box::pin(async move { self().await.into_response() })
123    }
124
125    fn update_operation(op: &mut Operation) {
126        Res::update_response(op);
127    }
128
129    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
130        Res::register_components(spec);
131    }
132}
133
134// 1 arg
135impl<F, Fut, Res, T1> Handler<(T1,)> for F
136where
137    F: FnOnce(T1) -> Fut + Clone + Send + Sync + 'static,
138    Fut: Future<Output = Res> + Send + 'static,
139    Res: IntoResponse + ResponseModifier,
140    T1: FromRequest + OperationModifier + Send + 'static,
141{
142    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
143
144    fn call(self, mut req: Request) -> Self::Future {
145        Box::pin(async move {
146            let t1 = match T1::from_request(&mut req).await {
147                Ok(v) => v,
148                Err(e) => return e.into_response(),
149            };
150            self(t1).await.into_response()
151        })
152    }
153
154    fn update_operation(op: &mut Operation) {
155        T1::update_operation(op);
156        Res::update_response(op);
157    }
158
159    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
160        T1::register_components(spec);
161        Res::register_components(spec);
162    }
163}
164
165// 2 args
166impl<F, Fut, Res, T1, T2> Handler<(T1, T2)> for F
167where
168    F: FnOnce(T1, T2) -> Fut + Clone + Send + Sync + 'static,
169    Fut: Future<Output = Res> + Send + 'static,
170    Res: IntoResponse + ResponseModifier,
171    T1: FromRequest + OperationModifier + Send + 'static,
172    T2: FromRequest + OperationModifier + Send + 'static,
173{
174    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
175
176    fn call(self, mut req: Request) -> Self::Future {
177        Box::pin(async move {
178            let t1 = match T1::from_request(&mut req).await {
179                Ok(v) => v,
180                Err(e) => return e.into_response(),
181            };
182            let t2 = match T2::from_request(&mut req).await {
183                Ok(v) => v,
184                Err(e) => return e.into_response(),
185            };
186            self(t1, t2).await.into_response()
187        })
188    }
189
190    fn update_operation(op: &mut Operation) {
191        T1::update_operation(op);
192        T2::update_operation(op);
193        Res::update_response(op);
194    }
195
196    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
197        T1::register_components(spec);
198        T2::register_components(spec);
199        Res::register_components(spec);
200    }
201}
202
203// 3 args
204impl<F, Fut, Res, T1, T2, T3> Handler<(T1, T2, T3)> for F
205where
206    F: FnOnce(T1, T2, T3) -> Fut + Clone + Send + Sync + 'static,
207    Fut: Future<Output = Res> + Send + 'static,
208    Res: IntoResponse + ResponseModifier,
209    T1: FromRequest + OperationModifier + Send + 'static,
210    T2: FromRequest + OperationModifier + Send + 'static,
211    T3: FromRequest + OperationModifier + Send + 'static,
212{
213    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
214
215    fn call(self, mut req: Request) -> Self::Future {
216        Box::pin(async move {
217            let t1 = match T1::from_request(&mut req).await {
218                Ok(v) => v,
219                Err(e) => return e.into_response(),
220            };
221            let t2 = match T2::from_request(&mut req).await {
222                Ok(v) => v,
223                Err(e) => return e.into_response(),
224            };
225            let t3 = match T3::from_request(&mut req).await {
226                Ok(v) => v,
227                Err(e) => return e.into_response(),
228            };
229            self(t1, t2, t3).await.into_response()
230        })
231    }
232
233    fn update_operation(op: &mut Operation) {
234        T1::update_operation(op);
235        T2::update_operation(op);
236        T3::update_operation(op);
237        Res::update_response(op);
238    }
239
240    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
241        T1::register_components(spec);
242        T2::register_components(spec);
243        T3::register_components(spec);
244        Res::register_components(spec);
245    }
246}
247
248// 4 args
249impl<F, Fut, Res, T1, T2, T3, T4> Handler<(T1, T2, T3, T4)> for F
250where
251    F: FnOnce(T1, T2, T3, T4) -> Fut + Clone + Send + Sync + 'static,
252    Fut: Future<Output = Res> + Send + 'static,
253    Res: IntoResponse + ResponseModifier,
254    T1: FromRequest + OperationModifier + Send + 'static,
255    T2: FromRequest + OperationModifier + Send + 'static,
256    T3: FromRequest + OperationModifier + Send + 'static,
257    T4: FromRequest + OperationModifier + Send + 'static,
258{
259    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
260
261    fn call(self, mut req: Request) -> Self::Future {
262        Box::pin(async move {
263            let t1 = match T1::from_request(&mut req).await {
264                Ok(v) => v,
265                Err(e) => return e.into_response(),
266            };
267            let t2 = match T2::from_request(&mut req).await {
268                Ok(v) => v,
269                Err(e) => return e.into_response(),
270            };
271            let t3 = match T3::from_request(&mut req).await {
272                Ok(v) => v,
273                Err(e) => return e.into_response(),
274            };
275            let t4 = match T4::from_request(&mut req).await {
276                Ok(v) => v,
277                Err(e) => return e.into_response(),
278            };
279            self(t1, t2, t3, t4).await.into_response()
280        })
281    }
282
283    fn update_operation(op: &mut Operation) {
284        T1::update_operation(op);
285        T2::update_operation(op);
286        T3::update_operation(op);
287        T4::update_operation(op);
288        Res::update_response(op);
289    }
290
291    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
292        T1::register_components(spec);
293        T2::register_components(spec);
294        T3::register_components(spec);
295        T4::register_components(spec);
296        Res::register_components(spec);
297    }
298}
299
300// 5 args
301impl<F, Fut, Res, T1, T2, T3, T4, T5> Handler<(T1, T2, T3, T4, T5)> for F
302where
303    F: FnOnce(T1, T2, T3, T4, T5) -> Fut + Clone + Send + Sync + 'static,
304    Fut: Future<Output = Res> + Send + 'static,
305    Res: IntoResponse + ResponseModifier,
306    T1: FromRequest + OperationModifier + Send + 'static,
307    T2: FromRequest + OperationModifier + Send + 'static,
308    T3: FromRequest + OperationModifier + Send + 'static,
309    T4: FromRequest + OperationModifier + Send + 'static,
310    T5: FromRequest + OperationModifier + Send + 'static,
311{
312    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
313
314    fn call(self, mut req: Request) -> Self::Future {
315        Box::pin(async move {
316            let t1 = match T1::from_request(&mut req).await {
317                Ok(v) => v,
318                Err(e) => return e.into_response(),
319            };
320            let t2 = match T2::from_request(&mut req).await {
321                Ok(v) => v,
322                Err(e) => return e.into_response(),
323            };
324            let t3 = match T3::from_request(&mut req).await {
325                Ok(v) => v,
326                Err(e) => return e.into_response(),
327            };
328            let t4 = match T4::from_request(&mut req).await {
329                Ok(v) => v,
330                Err(e) => return e.into_response(),
331            };
332            let t5 = match T5::from_request(&mut req).await {
333                Ok(v) => v,
334                Err(e) => return e.into_response(),
335            };
336            self(t1, t2, t3, t4, t5).await.into_response()
337        })
338    }
339
340    fn update_operation(op: &mut Operation) {
341        T1::update_operation(op);
342        T2::update_operation(op);
343        T3::update_operation(op);
344        T4::update_operation(op);
345        T5::update_operation(op);
346        Res::update_response(op);
347    }
348
349    fn register_components(spec: &mut rustapi_openapi::OpenApiSpec) {
350        T1::register_components(spec);
351        T2::register_components(spec);
352        T3::register_components(spec);
353        T4::register_components(spec);
354        T5::register_components(spec);
355        Res::register_components(spec);
356    }
357}
358
359// Type-erased handler for storage in router
360pub(crate) type BoxedHandler =
361    std::sync::Arc<dyn Fn(Request) -> Pin<Box<dyn Future<Output = Response> + Send>> + Send + Sync>;
362
363/// Create a boxed handler from any Handler
364pub(crate) fn into_boxed_handler<H, T>(handler: H) -> BoxedHandler
365where
366    H: Handler<T>,
367    T: 'static,
368{
369    std::sync::Arc::new(move |req| {
370        let handler = handler.clone();
371        Box::pin(async move { handler.call(req).await })
372    })
373}
374
375/// Trait for handlers with route metadata (generated by `#[rustapi::get]`, etc.)
376///
377/// This trait provides the path and method information for a handler,
378/// allowing `.mount(handler)` to automatically register the route.
379pub trait RouteHandler<T>: Handler<T> {
380    /// The path pattern for this route (e.g., "/users/{id}")
381    const PATH: &'static str;
382    /// The HTTP method for this route (e.g., "GET")
383    const METHOD: &'static str;
384}
385
386/// Represents a route definition that can be registered with .mount()
387pub struct Route {
388    pub(crate) path: &'static str,
389    pub(crate) method: &'static str,
390    pub(crate) handler: BoxedHandler,
391    pub(crate) operation: Operation,
392    pub(crate) component_registrar: fn(&mut rustapi_openapi::OpenApiSpec),
393    /// Custom parameter schemas for OpenAPI (param_name -> schema_type)
394    /// Supported types: "uuid", "integer", "string", "boolean", "number"
395    pub(crate) param_schemas: std::collections::BTreeMap<String, String>,
396    /// Custom error responses for OpenAPI (status_code -> description)
397    pub(crate) error_responses: Vec<(u16, String)>,
398}
399
400impl Route {
401    /// Create a new route from a handler with path and method
402    pub fn new<H, T>(path: &'static str, method: &'static str, handler: H) -> Self
403    where
404        H: Handler<T>,
405        T: 'static,
406    {
407        let mut operation = Operation::new();
408        H::update_operation(&mut operation);
409
410        Self {
411            path,
412            method,
413            handler: into_boxed_handler(handler),
414            operation,
415            component_registrar: <H as Handler<T>>::register_components,
416            param_schemas: std::collections::BTreeMap::new(),
417            error_responses: Vec::new(),
418        }
419    }
420    /// Set the operation summary
421    pub fn summary(mut self, summary: impl Into<String>) -> Self {
422        self.operation = self.operation.summary(summary);
423        self
424    }
425
426    /// Set the operation description
427    pub fn description(mut self, description: impl Into<String>) -> Self {
428        self.operation = self.operation.description(description);
429        self
430    }
431
432    /// Add a tag to the operation
433    pub fn tag(mut self, tag: impl Into<String>) -> Self {
434        self.operation.tags.push(tag.into());
435        self
436    }
437
438    /// Get the route path
439    pub fn path(&self) -> &str {
440        self.path
441    }
442
443    /// Get the route method
444    pub fn method(&self) -> &str {
445        self.method
446    }
447
448    /// Set a custom OpenAPI schema type for a path parameter
449    ///
450    /// This is useful for overriding the auto-inferred type, e.g., when
451    /// a parameter named `id` is actually a UUID instead of an integer.
452    ///
453    /// # Supported schema types
454    /// - `"uuid"` - String with UUID format
455    /// - `"integer"` or `"int"` - Integer with int64 format
456    /// - `"string"` - Plain string
457    /// - `"boolean"` or `"bool"` - Boolean
458    /// - `"number"` - Number (float)
459    ///
460    /// # Example
461    ///
462    /// ```rust,ignore
463    /// #[rustapi::get("/users/{id}")]
464    /// async fn get_user(Path(id): Path<Uuid>) -> Json<User> {
465    ///     // ...
466    /// }
467    ///
468    /// // In route registration:
469    /// get_route("/users/{id}", get_user).param("id", "uuid")
470    /// ```
471    pub fn param(mut self, name: impl Into<String>, schema_type: impl Into<String>) -> Self {
472        self.param_schemas.insert(name.into(), schema_type.into());
473        self
474    }
475
476    /// Get the custom parameter schemas
477    pub fn param_schemas(&self) -> &std::collections::BTreeMap<String, String> {
478        &self.param_schemas
479    }
480
481    /// Add a typed error response to the OpenAPI specification for this route
482    ///
483    /// This adds an error response entry to the operation's responses map,
484    /// referencing the standard `ErrorSchema` component.
485    ///
486    /// # Arguments
487    ///
488    /// * `status` - HTTP status code (e.g., 404, 403, 409)
489    /// * `description` - Human-readable description of when this error occurs
490    ///
491    /// # Example
492    ///
493    /// ```rust,ignore
494    /// get_route("/users/{id}", get_user)
495    ///     .error_response(404, "User not found")
496    ///     .error_response(403, "Forbidden - insufficient permissions")
497    /// ```
498    pub fn error_response(mut self, status: u16, description: impl Into<String>) -> Self {
499        let desc = description.into();
500        self.error_responses.push((status, desc.clone()));
501
502        // Also add directly to the operation's responses
503        let mut content = std::collections::BTreeMap::new();
504        content.insert(
505            "application/json".to_string(),
506            rustapi_openapi::MediaType {
507                schema: Some(rustapi_openapi::SchemaRef::Ref {
508                    reference: "#/components/schemas/ErrorSchema".to_string(),
509                }),
510                example: None,
511            },
512        );
513
514        self.operation.responses.insert(
515            status.to_string(),
516            rustapi_openapi::ResponseSpec {
517                description: desc,
518                content,
519                headers: std::collections::BTreeMap::new(),
520            },
521        );
522
523        self
524    }
525
526    /// Get the custom error responses
527    pub fn error_responses(&self) -> &[(u16, String)] {
528        &self.error_responses
529    }
530}
531
532/// Helper macro to create a Route from a handler with RouteHandler trait
533#[macro_export]
534macro_rules! route {
535    ($handler:ident) => {{
536        $crate::Route::new($handler::PATH, $handler::METHOD, $handler)
537    }};
538}
539
540/// Create a GET route
541pub fn get_route<H, T>(path: &'static str, handler: H) -> Route
542where
543    H: Handler<T>,
544    T: 'static,
545{
546    Route::new(path, "GET", handler)
547}
548
549/// Create a POST route
550pub fn post_route<H, T>(path: &'static str, handler: H) -> Route
551where
552    H: Handler<T>,
553    T: 'static,
554{
555    Route::new(path, "POST", handler)
556}
557
558/// Create a PUT route
559pub fn put_route<H, T>(path: &'static str, handler: H) -> Route
560where
561    H: Handler<T>,
562    T: 'static,
563{
564    Route::new(path, "PUT", handler)
565}
566
567/// Create a PATCH route
568pub fn patch_route<H, T>(path: &'static str, handler: H) -> Route
569where
570    H: Handler<T>,
571    T: 'static,
572{
573    Route::new(path, "PATCH", handler)
574}
575
576/// Create a DELETE route
577pub fn delete_route<H, T>(path: &'static str, handler: H) -> Route
578where
579    H: Handler<T>,
580    T: 'static,
581{
582    Route::new(path, "DELETE", handler)
583}