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
83/// Wrapper to convert a Handler into a tower Service
84pub struct HandlerService<H, T> {
85    handler: H,
86    _marker: PhantomData<fn() -> T>,
87}
88
89impl<H, T> HandlerService<H, T> {
90    pub fn new(handler: H) -> Self {
91        Self {
92            handler,
93            _marker: PhantomData,
94        }
95    }
96}
97
98impl<H: Clone, T> Clone for HandlerService<H, T> {
99    fn clone(&self) -> Self {
100        Self {
101            handler: self.handler.clone(),
102            _marker: PhantomData,
103        }
104    }
105}
106
107// Implement Handler for async functions with 0-6 extractors
108
109// 0 args
110impl<F, Fut, Res> Handler<()> for F
111where
112    F: FnOnce() -> Fut + Clone + Send + Sync + 'static,
113    Fut: Future<Output = Res> + Send + 'static,
114    Res: IntoResponse + ResponseModifier,
115{
116    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
117
118    fn call(self, _req: Request) -> Self::Future {
119        Box::pin(async move { self().await.into_response() })
120    }
121
122    fn update_operation(op: &mut Operation) {
123        Res::update_response(op);
124    }
125}
126
127// 1 arg
128impl<F, Fut, Res, T1> Handler<(T1,)> for F
129where
130    F: FnOnce(T1) -> Fut + Clone + Send + Sync + 'static,
131    Fut: Future<Output = Res> + Send + 'static,
132    Res: IntoResponse + ResponseModifier,
133    T1: FromRequest + OperationModifier + Send + 'static,
134{
135    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
136
137    fn call(self, mut req: Request) -> Self::Future {
138        Box::pin(async move {
139            let t1 = match T1::from_request(&mut req).await {
140                Ok(v) => v,
141                Err(e) => return e.into_response(),
142            };
143            self(t1).await.into_response()
144        })
145    }
146
147    fn update_operation(op: &mut Operation) {
148        T1::update_operation(op);
149        Res::update_response(op);
150    }
151}
152
153// 2 args
154impl<F, Fut, Res, T1, T2> Handler<(T1, T2)> for F
155where
156    F: FnOnce(T1, T2) -> Fut + Clone + Send + Sync + 'static,
157    Fut: Future<Output = Res> + Send + 'static,
158    Res: IntoResponse + ResponseModifier,
159    T1: FromRequest + OperationModifier + Send + 'static,
160    T2: FromRequest + OperationModifier + Send + 'static,
161{
162    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
163
164    fn call(self, mut req: Request) -> Self::Future {
165        Box::pin(async move {
166            let t1 = match T1::from_request(&mut req).await {
167                Ok(v) => v,
168                Err(e) => return e.into_response(),
169            };
170            let t2 = match T2::from_request(&mut req).await {
171                Ok(v) => v,
172                Err(e) => return e.into_response(),
173            };
174            self(t1, t2).await.into_response()
175        })
176    }
177
178    fn update_operation(op: &mut Operation) {
179        T1::update_operation(op);
180        T2::update_operation(op);
181        Res::update_response(op);
182    }
183}
184
185// 3 args
186impl<F, Fut, Res, T1, T2, T3> Handler<(T1, T2, T3)> for F
187where
188    F: FnOnce(T1, T2, T3) -> Fut + Clone + Send + Sync + 'static,
189    Fut: Future<Output = Res> + Send + 'static,
190    Res: IntoResponse + ResponseModifier,
191    T1: FromRequest + OperationModifier + Send + 'static,
192    T2: FromRequest + OperationModifier + Send + 'static,
193    T3: FromRequest + OperationModifier + Send + 'static,
194{
195    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
196
197    fn call(self, mut req: Request) -> Self::Future {
198        Box::pin(async move {
199            let t1 = match T1::from_request(&mut req).await {
200                Ok(v) => v,
201                Err(e) => return e.into_response(),
202            };
203            let t2 = match T2::from_request(&mut req).await {
204                Ok(v) => v,
205                Err(e) => return e.into_response(),
206            };
207            let t3 = match T3::from_request(&mut req).await {
208                Ok(v) => v,
209                Err(e) => return e.into_response(),
210            };
211            self(t1, t2, t3).await.into_response()
212        })
213    }
214
215    fn update_operation(op: &mut Operation) {
216        T1::update_operation(op);
217        T2::update_operation(op);
218        T3::update_operation(op);
219        Res::update_response(op);
220    }
221}
222
223// 4 args
224impl<F, Fut, Res, T1, T2, T3, T4> Handler<(T1, T2, T3, T4)> for F
225where
226    F: FnOnce(T1, T2, T3, T4) -> Fut + Clone + Send + Sync + 'static,
227    Fut: Future<Output = Res> + Send + 'static,
228    Res: IntoResponse + ResponseModifier,
229    T1: FromRequest + OperationModifier + Send + 'static,
230    T2: FromRequest + OperationModifier + Send + 'static,
231    T3: FromRequest + OperationModifier + Send + 'static,
232    T4: FromRequest + OperationModifier + Send + 'static,
233{
234    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
235
236    fn call(self, mut req: Request) -> Self::Future {
237        Box::pin(async move {
238            let t1 = match T1::from_request(&mut req).await {
239                Ok(v) => v,
240                Err(e) => return e.into_response(),
241            };
242            let t2 = match T2::from_request(&mut req).await {
243                Ok(v) => v,
244                Err(e) => return e.into_response(),
245            };
246            let t3 = match T3::from_request(&mut req).await {
247                Ok(v) => v,
248                Err(e) => return e.into_response(),
249            };
250            let t4 = match T4::from_request(&mut req).await {
251                Ok(v) => v,
252                Err(e) => return e.into_response(),
253            };
254            self(t1, t2, t3, t4).await.into_response()
255        })
256    }
257
258    fn update_operation(op: &mut Operation) {
259        T1::update_operation(op);
260        T2::update_operation(op);
261        T3::update_operation(op);
262        T4::update_operation(op);
263        Res::update_response(op);
264    }
265}
266
267// 5 args
268impl<F, Fut, Res, T1, T2, T3, T4, T5> Handler<(T1, T2, T3, T4, T5)> for F
269where
270    F: FnOnce(T1, T2, T3, T4, T5) -> Fut + Clone + Send + Sync + 'static,
271    Fut: Future<Output = Res> + Send + 'static,
272    Res: IntoResponse + ResponseModifier,
273    T1: FromRequest + OperationModifier + Send + 'static,
274    T2: FromRequest + OperationModifier + Send + 'static,
275    T3: FromRequest + OperationModifier + Send + 'static,
276    T4: FromRequest + OperationModifier + Send + 'static,
277    T5: FromRequest + OperationModifier + Send + 'static,
278{
279    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
280
281    fn call(self, mut req: Request) -> Self::Future {
282        Box::pin(async move {
283            let t1 = match T1::from_request(&mut req).await {
284                Ok(v) => v,
285                Err(e) => return e.into_response(),
286            };
287            let t2 = match T2::from_request(&mut req).await {
288                Ok(v) => v,
289                Err(e) => return e.into_response(),
290            };
291            let t3 = match T3::from_request(&mut req).await {
292                Ok(v) => v,
293                Err(e) => return e.into_response(),
294            };
295            let t4 = match T4::from_request(&mut req).await {
296                Ok(v) => v,
297                Err(e) => return e.into_response(),
298            };
299            let t5 = match T5::from_request(&mut req).await {
300                Ok(v) => v,
301                Err(e) => return e.into_response(),
302            };
303            self(t1, t2, t3, t4, t5).await.into_response()
304        })
305    }
306
307    fn update_operation(op: &mut Operation) {
308        T1::update_operation(op);
309        T2::update_operation(op);
310        T3::update_operation(op);
311        T4::update_operation(op);
312        T5::update_operation(op);
313        Res::update_response(op);
314    }
315}
316
317// Type-erased handler for storage in router
318pub(crate) type BoxedHandler =
319    std::sync::Arc<dyn Fn(Request) -> Pin<Box<dyn Future<Output = Response> + Send>> + Send + Sync>;
320
321/// Create a boxed handler from any Handler
322pub(crate) fn into_boxed_handler<H, T>(handler: H) -> BoxedHandler
323where
324    H: Handler<T>,
325    T: 'static,
326{
327    std::sync::Arc::new(move |req| {
328        let handler = handler.clone();
329        Box::pin(async move { handler.call(req).await })
330    })
331}
332
333/// Trait for handlers with route metadata (generated by `#[rustapi::get]`, etc.)
334///
335/// This trait provides the path and method information for a handler,
336/// allowing `.mount(handler)` to automatically register the route.
337pub trait RouteHandler<T>: Handler<T> {
338    /// The path pattern for this route (e.g., "/users/{id}")
339    const PATH: &'static str;
340    /// The HTTP method for this route (e.g., "GET")
341    const METHOD: &'static str;
342}
343
344/// Represents a route definition that can be registered with .mount()
345pub struct Route {
346    pub(crate) path: &'static str,
347    pub(crate) method: &'static str,
348    pub(crate) handler: BoxedHandler,
349    pub(crate) operation: Operation,
350    /// Custom parameter schemas for OpenAPI (param_name -> schema_type)
351    /// Supported types: "uuid", "integer", "string", "boolean", "number"
352    pub(crate) param_schemas: std::collections::BTreeMap<String, String>,
353}
354
355impl Route {
356    /// Create a new route from a handler with path and method
357    pub fn new<H, T>(path: &'static str, method: &'static str, handler: H) -> Self
358    where
359        H: Handler<T>,
360        T: 'static,
361    {
362        let mut operation = Operation::new();
363        H::update_operation(&mut operation);
364
365        Self {
366            path,
367            method,
368            handler: into_boxed_handler(handler),
369            operation,
370            param_schemas: std::collections::BTreeMap::new(),
371        }
372    }
373    /// Set the operation summary
374    pub fn summary(mut self, summary: impl Into<String>) -> Self {
375        self.operation = self.operation.summary(summary);
376        self
377    }
378
379    /// Set the operation description
380    pub fn description(mut self, description: impl Into<String>) -> Self {
381        self.operation = self.operation.description(description);
382        self
383    }
384
385    /// Add a tag to the operation
386    pub fn tag(mut self, tag: impl Into<String>) -> Self {
387        self.operation.tags.push(tag.into());
388        self
389    }
390
391    /// Get the route path
392    pub fn path(&self) -> &str {
393        self.path
394    }
395
396    /// Get the route method
397    pub fn method(&self) -> &str {
398        self.method
399    }
400
401    /// Set a custom OpenAPI schema type for a path parameter
402    ///
403    /// This is useful for overriding the auto-inferred type, e.g., when
404    /// a parameter named `id` is actually a UUID instead of an integer.
405    ///
406    /// # Supported schema types
407    /// - `"uuid"` - String with UUID format
408    /// - `"integer"` or `"int"` - Integer with int64 format
409    /// - `"string"` - Plain string
410    /// - `"boolean"` or `"bool"` - Boolean
411    /// - `"number"` - Number (float)
412    ///
413    /// # Example
414    ///
415    /// ```rust,ignore
416    /// #[rustapi::get("/users/{id}")]
417    /// async fn get_user(Path(id): Path<Uuid>) -> Json<User> {
418    ///     // ...
419    /// }
420    ///
421    /// // In route registration:
422    /// get_route("/users/{id}", get_user).param("id", "uuid")
423    /// ```
424    pub fn param(mut self, name: impl Into<String>, schema_type: impl Into<String>) -> Self {
425        self.param_schemas.insert(name.into(), schema_type.into());
426        self
427    }
428
429    /// Get the custom parameter schemas
430    pub fn param_schemas(&self) -> &std::collections::BTreeMap<String, String> {
431        &self.param_schemas
432    }
433}
434
435/// Helper macro to create a Route from a handler with RouteHandler trait
436#[macro_export]
437macro_rules! route {
438    ($handler:ident) => {{
439        $crate::Route::new($handler::PATH, $handler::METHOD, $handler)
440    }};
441}
442
443/// Create a GET route
444pub fn get_route<H, T>(path: &'static str, handler: H) -> Route
445where
446    H: Handler<T>,
447    T: 'static,
448{
449    Route::new(path, "GET", handler)
450}
451
452/// Create a POST route
453pub fn post_route<H, T>(path: &'static str, handler: H) -> Route
454where
455    H: Handler<T>,
456    T: 'static,
457{
458    Route::new(path, "POST", handler)
459}
460
461/// Create a PUT route
462pub fn put_route<H, T>(path: &'static str, handler: H) -> Route
463where
464    H: Handler<T>,
465    T: 'static,
466{
467    Route::new(path, "PUT", handler)
468}
469
470/// Create a PATCH route
471pub fn patch_route<H, T>(path: &'static str, handler: H) -> Route
472where
473    H: Handler<T>,
474    T: 'static,
475{
476    Route::new(path, "PATCH", handler)
477}
478
479/// Create a DELETE route
480pub fn delete_route<H, T>(path: &'static str, handler: H) -> Route
481where
482    H: Handler<T>,
483    T: 'static,
484{
485    Route::new(path, "DELETE", handler)
486}