armature_core/
handler.rs

1// Optimized handler dispatch system for maximum performance
2//
3// This module provides a handler system that enables:
4// - Monomorphization: Handlers are specialized at compile time for each unique handler type
5// - Inline dispatch: Hot paths use #[inline(always)] to eliminate function call overhead
6// - Zero-cost abstractions: No runtime overhead compared to hand-written handlers
7//
8// The design follows Axum's approach: use traits with associated types for the Future,
9// then type-erase at storage time while keeping invocation monomorphized.
10
11use crate::logging::trace;
12use crate::{Error, HttpRequest, HttpResponse};
13use std::future::Future;
14use std::marker::PhantomData;
15use std::pin::Pin;
16use std::sync::Arc;
17
18/// A handler that can process HTTP requests.
19///
20/// This trait is designed to be monomorphized: each implementation gets its own
21/// specialized code path, allowing the compiler to inline the handler body.
22///
23/// # Example
24///
25/// ```ignore
26/// async fn my_handler(req: HttpRequest) -> Result<HttpResponse, Error> {
27///     Ok(HttpResponse::ok())
28/// }
29///
30/// // The handler is automatically converted via IntoHandler
31/// let handler = my_handler.into_handler();
32/// ```
33pub trait Handler: Clone + Send + Sync + 'static {
34    /// The future returned by `call`.
35    ///
36    /// Using an associated type instead of `Box<dyn Future>` allows the compiler
37    /// to monomorphize this future type, enabling inlining.
38    type Future: Future<Output = Result<HttpResponse, Error>> + Send + 'static;
39
40    /// Handle an HTTP request.
41    ///
42    /// This method should be inlined by the compiler due to monomorphization.
43    /// Note: #[inline] hints are applied in implementations, not trait definitions.
44    fn call(&self, req: HttpRequest) -> Self::Future;
45}
46
47/// Trait for converting various function types into handlers.
48///
49/// This enables ergonomic handler registration:
50/// ```ignore
51/// router.get("/", my_handler);  // Works for any IntoHandler
52/// ```
53pub trait IntoHandler<Args>: Clone + Send + Sync + 'static {
54    /// The handler type this converts into.
55    type Handler: Handler;
56
57    /// Convert into a handler.
58    fn into_handler(self) -> Self::Handler;
59}
60
61/// A function handler that wraps an async function.
62///
63/// This is the primary handler type, wrapping `async fn(HttpRequest) -> Result<HttpResponse, Error>`.
64#[derive(Clone)]
65pub struct FnHandler<F> {
66    f: F,
67}
68
69impl<F> FnHandler<F> {
70    /// Create a new function handler.
71    #[inline(always)]
72    pub fn new(f: F) -> Self {
73        Self { f }
74    }
75}
76
77impl<F, Fut> Handler for FnHandler<F>
78where
79    F: Fn(HttpRequest) -> Fut + Clone + Send + Sync + 'static,
80    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
81{
82    type Future = Fut;
83
84    #[inline(always)]
85    fn call(&self, req: HttpRequest) -> Self::Future {
86        (self.f)(req)
87    }
88}
89
90/// Implement IntoHandler for async functions.
91impl<F, Fut> IntoHandler<(HttpRequest,)> for F
92where
93    F: Fn(HttpRequest) -> Fut + Clone + Send + Sync + 'static,
94    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
95{
96    type Handler = FnHandler<F>;
97
98    #[inline(always)]
99    fn into_handler(self) -> Self::Handler {
100        FnHandler::new(self)
101    }
102}
103
104/// Type-erased handler for storing in collections.
105///
106/// While `Handler` uses associated types for zero-cost abstraction,
107/// we need type erasure to store different handlers in the same `Vec`.
108///
109/// This type performs the erasure while keeping the actual handler call
110/// as optimized as possible via vtable dispatch with inlined inner handlers.
111pub struct BoxedHandler {
112    inner: Arc<dyn ErasedHandler>,
113}
114
115impl BoxedHandler {
116    /// Create a new boxed handler from any Handler.
117    ///
118    /// The handler is wrapped in an Arc for cheap cloning.
119    #[inline]
120    pub fn new<H: Handler>(handler: H) -> Self {
121        Self {
122            inner: Arc::new(HandlerWrapper {
123                handler,
124                _marker: PhantomData,
125            }),
126        }
127    }
128
129    /// Call the handler.
130    ///
131    /// This goes through a vtable but the actual handler implementation
132    /// is monomorphized and can be inlined within the wrapper.
133    #[inline(always)]
134    pub fn call(
135        &self,
136        req: HttpRequest,
137    ) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>> {
138        trace!(path = %req.path, method = %req.method, "Handler dispatch");
139        self.inner.call(req)
140    }
141}
142
143impl Clone for BoxedHandler {
144    #[inline]
145    fn clone(&self) -> Self {
146        Self {
147            inner: self.inner.clone(),
148        }
149    }
150}
151
152/// Internal trait for type-erased handler dispatch.
153///
154/// This trait enables storing different handler types together while
155/// keeping the vtable overhead minimal.
156trait ErasedHandler: Send + Sync {
157    fn call(
158        &self,
159        req: HttpRequest,
160    ) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
161}
162
163/// Wrapper that implements ErasedHandler for any Handler.
164///
165/// The key optimization is that `handler.call(req)` is monomorphized
166/// for each concrete handler type, so the compiler can inline the
167/// handler body even though we're going through a vtable.
168struct HandlerWrapper<H: Handler> {
169    handler: H,
170    _marker: PhantomData<fn() -> H::Future>,
171}
172
173// Safety: HandlerWrapper is Send + Sync if H is Send + Sync
174unsafe impl<H: Handler> Send for HandlerWrapper<H> {}
175unsafe impl<H: Handler> Sync for HandlerWrapper<H> {}
176
177impl<H: Handler> ErasedHandler for HandlerWrapper<H> {
178    #[inline(always)]
179    fn call(
180        &self,
181        req: HttpRequest,
182    ) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>> {
183        // This call to handler.call() is monomorphized for the specific H type.
184        // The compiler sees the concrete handler type and can inline its body.
185        Box::pin(self.handler.call(req))
186    }
187}
188
189/// Optimized handler type alias for the routing system.
190///
191/// This replaces the old `HandlerFn` type with a more optimized version
192/// that enables monomorphization of the inner handler.
193pub type OptimizedHandlerFn = BoxedHandler;
194
195/// Create an optimized handler from a function.
196///
197/// # Example
198///
199/// ```ignore
200/// use armature_core::handler::handler;
201///
202/// async fn my_handler(req: HttpRequest) -> Result<HttpResponse, Error> {
203///     Ok(HttpResponse::ok())
204/// }
205///
206/// let h = handler(my_handler);
207/// ```
208#[inline]
209pub fn handler<H, Args>(h: H) -> BoxedHandler
210where
211    H: IntoHandler<Args>,
212{
213    BoxedHandler::new(h.into_handler())
214}
215
216/// Legacy handler function type alias.
217///
218/// This type uses double dynamic dispatch (dyn Fn + Box<dyn Future>) which
219/// prevents inlining. Prefer using the optimized handler system.
220#[allow(clippy::type_complexity)]
221pub type LegacyHandlerFn = Arc<
222    dyn Fn(HttpRequest) -> Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>
223        + Send
224        + Sync,
225>;
226
227/// Convert the legacy HandlerFn type to a BoxedHandler.
228///
229/// This provides backwards compatibility while encouraging migration
230/// to the new handler system.
231#[inline]
232pub fn from_legacy_handler(f: LegacyHandlerFn) -> BoxedHandler {
233    BoxedHandler::new(LegacyHandler { f })
234}
235
236/// Wrapper for legacy handler functions.
237#[derive(Clone)]
238struct LegacyHandler {
239    f: LegacyHandlerFn,
240}
241
242impl Handler for LegacyHandler {
243    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
244
245    #[inline(always)]
246    fn call(&self, req: HttpRequest) -> Self::Future {
247        (self.f)(req)
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    async fn test_handler(_req: HttpRequest) -> Result<HttpResponse, Error> {
256        Ok(HttpResponse::ok())
257    }
258
259    #[tokio::test]
260    async fn test_fn_handler() {
261        let handler = FnHandler::new(test_handler);
262        let req = HttpRequest::new("GET".to_string(), "/test".to_string());
263        let response = handler.call(req).await.unwrap();
264        assert_eq!(response.status, 200);
265    }
266
267    #[tokio::test]
268    async fn test_into_handler() {
269        let handler = test_handler.into_handler();
270        let req = HttpRequest::new("GET".to_string(), "/test".to_string());
271        let response = handler.call(req).await.unwrap();
272        assert_eq!(response.status, 200);
273    }
274
275    #[tokio::test]
276    async fn test_boxed_handler() {
277        let boxed = BoxedHandler::new(test_handler.into_handler());
278        let req = HttpRequest::new("GET".to_string(), "/test".to_string());
279        let response = boxed.call(req).await.unwrap();
280        assert_eq!(response.status, 200);
281    }
282
283    #[tokio::test]
284    async fn test_handler_fn() {
285        let h = handler(test_handler);
286        let req = HttpRequest::new("GET".to_string(), "/test".to_string());
287        let response = h.call(req).await.unwrap();
288        assert_eq!(response.status, 200);
289    }
290
291    #[tokio::test]
292    async fn test_clone_boxed_handler() {
293        let h1 = handler(test_handler);
294        let h2 = h1.clone();
295
296        let req1 = HttpRequest::new("GET".to_string(), "/test".to_string());
297        let req2 = HttpRequest::new("GET".to_string(), "/test".to_string());
298
299        let r1 = h1.call(req1).await.unwrap();
300        let r2 = h2.call(req2).await.unwrap();
301
302        assert_eq!(r1.status, 200);
303        assert_eq!(r2.status, 200);
304    }
305
306    #[test]
307    fn test_handler_is_send_sync() {
308        fn assert_send_sync<T: Send + Sync>() {}
309        assert_send_sync::<BoxedHandler>();
310    }
311}