Skip to main content

armature_core/
zero_cost.rs

1//! Zero-Cost Abstractions for High-Performance HTTP Handling
2//!
3//! This module moves dispatch decisions to compile time:
4//!
5//! - **Const Generic Extractors**: Combine multiple extractors at compile-time
6//! - **Static Dispatch Middleware**: Middleware without `Box<dyn>` overhead
7//!
8//! # Philosophy
9//!
10//! These abstractions follow the Rust principle of "zero-cost abstractions" -
11//! you don't pay for what you don't use, and what you do use is as efficient
12//! as hand-written code.
13//!
14//! # Performance Impact
15//!
16//! - Extractor chains: Inline extraction, no boxing per extractor
17//! - Static middleware: Compile-time dispatch, no vtable lookups
18//!
19//! Two caveats on "zero cost", both real and both measurable:
20//!
21//! - Every `Extract::extract` bumps a process-global counter
22//!   ([`ZeroCostStats`]). It is a relaxed `fetch_add`, but it is a shared cache
23//!   line and it is not feature-gated, so it is not free under contention.
24//! - "No heap allocation" holds for the extractors that hand back borrowed or
25//!   `Copy` data. [`PathParam`], [`Header`], [`ContentType`], and
26//!   [`Authorization`] each produce an owned `String`, because the value they
27//!   return has to outlive the borrow of the request.
28
29use crate::{Error, HttpRequest, HttpResponse};
30use std::future::Future;
31use std::marker::PhantomData;
32use std::pin::Pin;
33use std::sync::atomic::{AtomicU64, Ordering};
34
35// ============================================================================
36// Const Generic Extractor Chains
37// ============================================================================
38
39/// Trait for extractors that work with const generics.
40pub trait Extract: Sized {
41    /// Extract from request.
42    fn extract(req: &HttpRequest) -> Result<Self, Error>;
43}
44
45/// A single extractor wrapper.
46#[derive(Debug)]
47pub struct One<E: Extract>(pub E);
48
49impl<E: Extract> Extract for One<E> {
50    #[inline]
51    fn extract(req: &HttpRequest) -> Result<Self, Error> {
52        E::extract(req).map(One)
53    }
54}
55
56/// Two extractors combined.
57#[derive(Debug)]
58pub struct Two<E1: Extract, E2: Extract>(pub E1, pub E2);
59
60impl<E1: Extract, E2: Extract> Extract for Two<E1, E2> {
61    #[inline]
62    fn extract(req: &HttpRequest) -> Result<Self, Error> {
63        let e1 = E1::extract(req)?;
64        let e2 = E2::extract(req)?;
65        Ok(Two(e1, e2))
66    }
67}
68
69/// Three extractors combined.
70#[derive(Debug)]
71pub struct Three<E1: Extract, E2: Extract, E3: Extract>(pub E1, pub E2, pub E3);
72
73impl<E1: Extract, E2: Extract, E3: Extract> Extract for Three<E1, E2, E3> {
74    #[inline]
75    fn extract(req: &HttpRequest) -> Result<Self, Error> {
76        let e1 = E1::extract(req)?;
77        let e2 = E2::extract(req)?;
78        let e3 = E3::extract(req)?;
79        Ok(Three(e1, e2, e3))
80    }
81}
82
83/// Four extractors combined.
84#[derive(Debug)]
85pub struct Four<E1: Extract, E2: Extract, E3: Extract, E4: Extract>(pub E1, pub E2, pub E3, pub E4);
86
87impl<E1: Extract, E2: Extract, E3: Extract, E4: Extract> Extract for Four<E1, E2, E3, E4> {
88    #[inline]
89    fn extract(req: &HttpRequest) -> Result<Self, Error> {
90        let e1 = E1::extract(req)?;
91        let e2 = E2::extract(req)?;
92        let e3 = E3::extract(req)?;
93        let e4 = E4::extract(req)?;
94        Ok(Four(e1, e2, e3, e4))
95    }
96}
97
98/// Five extractors combined.
99#[derive(Debug)]
100pub struct Five<E1: Extract, E2: Extract, E3: Extract, E4: Extract, E5: Extract>(
101    pub E1,
102    pub E2,
103    pub E3,
104    pub E4,
105    pub E5,
106);
107
108impl<E1: Extract, E2: Extract, E3: Extract, E4: Extract, E5: Extract> Extract
109    for Five<E1, E2, E3, E4, E5>
110{
111    #[inline]
112    fn extract(req: &HttpRequest) -> Result<Self, Error> {
113        let e1 = E1::extract(req)?;
114        let e2 = E2::extract(req)?;
115        let e3 = E3::extract(req)?;
116        let e4 = E4::extract(req)?;
117        let e5 = E5::extract(req)?;
118        Ok(Five(e1, e2, e3, e4, e5))
119    }
120}
121
122// Implement Extract for tuples (ergonomic alternative)
123impl<E1: Extract, E2: Extract> Extract for (E1, E2) {
124    #[inline]
125    fn extract(req: &HttpRequest) -> Result<Self, Error> {
126        let e1 = E1::extract(req)?;
127        let e2 = E2::extract(req)?;
128        Ok((e1, e2))
129    }
130}
131
132impl<E1: Extract, E2: Extract, E3: Extract> Extract for (E1, E2, E3) {
133    #[inline]
134    fn extract(req: &HttpRequest) -> Result<Self, Error> {
135        let e1 = E1::extract(req)?;
136        let e2 = E2::extract(req)?;
137        let e3 = E3::extract(req)?;
138        Ok((e1, e2, e3))
139    }
140}
141
142impl<E1: Extract, E2: Extract, E3: Extract, E4: Extract> Extract for (E1, E2, E3, E4) {
143    #[inline]
144    fn extract(req: &HttpRequest) -> Result<Self, Error> {
145        let e1 = E1::extract(req)?;
146        let e2 = E2::extract(req)?;
147        let e3 = E3::extract(req)?;
148        let e4 = E4::extract(req)?;
149        Ok((e1, e2, e3, e4))
150    }
151}
152
153impl<E1: Extract, E2: Extract, E3: Extract, E4: Extract, E5: Extract> Extract
154    for (E1, E2, E3, E4, E5)
155{
156    #[inline]
157    fn extract(req: &HttpRequest) -> Result<Self, Error> {
158        let e1 = E1::extract(req)?;
159        let e2 = E2::extract(req)?;
160        let e3 = E3::extract(req)?;
161        let e4 = E4::extract(req)?;
162        let e5 = E5::extract(req)?;
163        Ok((e1, e2, e3, e4, e5))
164    }
165}
166
167// ============================================================================
168// Common Extractor Implementations
169// ============================================================================
170
171/// Extract the raw request body as bytes.
172#[derive(Debug, Clone)]
173pub struct RawBody(pub bytes::Bytes);
174
175impl Extract for RawBody {
176    #[inline]
177    fn extract(req: &HttpRequest) -> Result<Self, Error> {
178        EXTRACTOR_STATS.record_extraction();
179        Ok(RawBody(req.body_bytes()))
180    }
181}
182
183impl std::ops::Deref for RawBody {
184    type Target = bytes::Bytes;
185
186    fn deref(&self) -> &Self::Target {
187        &self.0
188    }
189}
190
191/// Extract JSON body with compile-time type.
192#[derive(Debug, Clone)]
193pub struct JsonBody<T>(pub T);
194
195impl<T: serde::de::DeserializeOwned> Extract for JsonBody<T> {
196    #[inline]
197    fn extract(req: &HttpRequest) -> Result<Self, Error> {
198        EXTRACTOR_STATS.record_extraction();
199        req.json().map(JsonBody)
200    }
201}
202
203impl<T> std::ops::Deref for JsonBody<T> {
204    type Target = T;
205
206    fn deref(&self) -> &Self::Target {
207        &self.0
208    }
209}
210
211/// Extract query parameters with compile-time type.
212#[derive(Debug, Clone)]
213pub struct QueryParams<T>(pub T);
214
215impl<T: serde::de::DeserializeOwned> Extract for QueryParams<T> {
216    #[inline]
217    fn extract(req: &HttpRequest) -> Result<Self, Error> {
218        EXTRACTOR_STATS.record_extraction();
219        // The raw query string, not the decoded pairs: re-encoding a decoded
220        // pair cannot always reproduce what the client sent.
221        serde_urlencoded::from_str(req.query_string().unwrap_or(""))
222            .map(QueryParams)
223            .map_err(|e| Error::Deserialization(format!("Query parsing error: {}", e)))
224    }
225}
226
227impl<T> std::ops::Deref for QueryParams<T> {
228    type Target = T;
229
230    fn deref(&self) -> &Self::Target {
231        &self.0
232    }
233}
234
235/// Extract a single path parameter by index.
236#[derive(Debug, Clone)]
237pub struct PathParam<const INDEX: usize>(pub String);
238
239impl<const INDEX: usize> Extract for PathParam<INDEX> {
240    #[inline]
241    fn extract(req: &HttpRequest) -> Result<Self, Error> {
242        EXTRACTOR_STATS.record_extraction();
243        // Get the Nth path parameter, in capture order.
244        req.path_params
245            .get(INDEX)
246            .and_then(|(_, v)| std::str::from_utf8(v).ok())
247            .map(|v| PathParam(v.to_owned()))
248            .ok_or_else(|| {
249                Error::RouteNotFound(format!("Path parameter at index {} not found", INDEX))
250            })
251    }
252}
253
254impl<const INDEX: usize> std::ops::Deref for PathParam<INDEX> {
255    type Target = String;
256
257    fn deref(&self) -> &Self::Target {
258        &self.0
259    }
260}
261
262/// Extract a single header by name.
263#[derive(Debug, Clone)]
264pub struct Header {
265    /// Header name.
266    pub name: String,
267    /// Header value (None if not found).
268    pub value: Option<String>,
269}
270
271impl Header {
272    /// Create extractor for a specific header.
273    pub fn named(name: impl Into<String>, req: &HttpRequest) -> Self {
274        let name = name.into();
275        let value = req.headers.get(&name).map(str::to_owned);
276        EXTRACTOR_STATS.record_extraction();
277        Self { name, value }
278    }
279
280    /// Get the header value or error if missing.
281    pub fn required(self) -> Result<String, Error> {
282        self.value
283            .ok_or_else(|| Error::Validation(format!("Required header '{}' not found", self.name)))
284    }
285
286    /// Get the header value or default.
287    pub fn unwrap_or(self, default: impl Into<String>) -> String {
288        self.value.unwrap_or_else(|| default.into())
289    }
290
291    /// Check if header is present.
292    pub fn is_present(&self) -> bool {
293        self.value.is_some()
294    }
295}
296
297/// Extract Content-Type header.
298#[derive(Debug, Clone)]
299pub struct ContentType(pub Option<String>);
300
301impl Extract for ContentType {
302    #[inline]
303    fn extract(req: &HttpRequest) -> Result<Self, Error> {
304        EXTRACTOR_STATS.record_extraction();
305        Ok(ContentType(
306            req.headers.get("content-type").map(str::to_owned),
307        ))
308    }
309}
310
311impl ContentType {
312    /// Check if content type is JSON.
313    pub fn is_json(&self) -> bool {
314        self.0
315            .as_ref()
316            .is_some_and(|v| v.contains("application/json"))
317    }
318
319    /// Get raw value.
320    pub fn value(&self) -> Option<&str> {
321        self.0.as_deref()
322    }
323}
324
325/// Extract Authorization header.
326#[derive(Debug, Clone)]
327pub struct Authorization(pub Option<String>);
328
329impl Extract for Authorization {
330    #[inline]
331    fn extract(req: &HttpRequest) -> Result<Self, Error> {
332        EXTRACTOR_STATS.record_extraction();
333        Ok(Authorization(
334            req.headers.get("authorization").map(str::to_owned),
335        ))
336    }
337}
338
339impl Authorization {
340    /// Get bearer token if present.
341    pub fn bearer(&self) -> Option<&str> {
342        self.0.as_ref().and_then(|v| v.strip_prefix("Bearer "))
343    }
344
345    /// Get basic auth credentials if present.
346    pub fn basic(&self) -> Option<&str> {
347        self.0.as_ref().and_then(|v| v.strip_prefix("Basic "))
348    }
349
350    /// Get raw value.
351    pub fn value(&self) -> Option<&str> {
352        self.0.as_deref()
353    }
354}
355
356/// Extract the HTTP method.
357#[derive(Debug, Clone)]
358pub struct Method(pub crate::Method);
359
360impl Extract for Method {
361    #[inline]
362    fn extract(req: &HttpRequest) -> Result<Self, Error> {
363        EXTRACTOR_STATS.record_extraction();
364        Ok(Method(req.method.clone()))
365    }
366}
367
368impl std::ops::Deref for Method {
369    type Target = str;
370
371    fn deref(&self) -> &Self::Target {
372        self.0.as_str()
373    }
374}
375
376/// Extract the request path.
377#[derive(Debug, Clone)]
378pub struct RequestPath(pub crate::ByteStr);
379
380impl Extract for RequestPath {
381    #[inline]
382    fn extract(req: &HttpRequest) -> Result<Self, Error> {
383        EXTRACTOR_STATS.record_extraction();
384        // The path without the query: `RequestPath` names the resource, and the
385        // query is reachable through `QueryParams`.
386        Ok(RequestPath(crate::ByteStr::from(req.path_only())))
387    }
388}
389
390impl std::ops::Deref for RequestPath {
391    type Target = str;
392
393    fn deref(&self) -> &Self::Target {
394        self.0.as_str()
395    }
396}
397
398/// Optional extractor - never fails, returns None if extraction fails.
399#[derive(Debug, Clone)]
400pub struct Optional<E>(pub Option<E>);
401
402impl<E: Extract> Extract for Optional<E> {
403    #[inline]
404    fn extract(req: &HttpRequest) -> Result<Self, Error> {
405        Ok(Optional(E::extract(req).ok()))
406    }
407}
408
409impl<E> std::ops::Deref for Optional<E> {
410    type Target = Option<E>;
411
412    fn deref(&self) -> &Self::Target {
413        &self.0
414    }
415}
416
417// ============================================================================
418// Static Dispatch Middleware
419// ============================================================================
420
421/// A middleware layer with static dispatch (no boxing).
422pub trait Layer<S> {
423    /// The wrapped service type.
424    type Service;
425
426    /// Wrap a service with this layer.
427    fn layer(&self, inner: S) -> Self::Service;
428}
429
430/// A service that can process requests.
431pub trait Service<Request> {
432    /// Response type.
433    type Response;
434    /// Error type.
435    type Error;
436    /// Future type.
437    type Future: Future<Output = Result<Self::Response, Self::Error>> + Send;
438
439    /// Process a request.
440    fn call(&self, req: Request) -> Self::Future;
441}
442
443/// Identity layer - does nothing, passes through.
444#[derive(Debug, Clone, Copy, Default)]
445pub struct Identity;
446
447impl<S> Layer<S> for Identity {
448    type Service = S;
449
450    #[inline]
451    fn layer(&self, inner: S) -> Self::Service {
452        inner
453    }
454}
455
456/// Stack two layers.
457#[derive(Debug, Clone)]
458pub struct Stack<Inner, Outer> {
459    inner: Inner,
460    outer: Outer,
461}
462
463impl<Inner, Outer> Stack<Inner, Outer> {
464    /// Create a new stack.
465    pub fn new(inner: Inner, outer: Outer) -> Self {
466        Self { inner, outer }
467    }
468}
469
470impl<S, Inner, Outer> Layer<S> for Stack<Inner, Outer>
471where
472    Inner: Layer<S>,
473    Outer: Layer<Inner::Service>,
474{
475    type Service = Outer::Service;
476
477    #[inline]
478    fn layer(&self, service: S) -> Self::Service {
479        let inner = self.inner.layer(service);
480        self.outer.layer(inner)
481    }
482}
483
484/// Builder for composing layers.
485#[derive(Debug, Clone)]
486pub struct LayerBuilder<L> {
487    layer: L,
488}
489
490impl LayerBuilder<Identity> {
491    /// Create a new layer builder.
492    pub fn new() -> Self {
493        Self { layer: Identity }
494    }
495}
496
497impl Default for LayerBuilder<Identity> {
498    fn default() -> Self {
499        Self::new()
500    }
501}
502
503impl<L> LayerBuilder<L> {
504    /// Add a layer to the stack.
505    pub fn layer<NewLayer>(self, new_layer: NewLayer) -> LayerBuilder<Stack<L, NewLayer>> {
506        LayerBuilder {
507            layer: Stack::new(self.layer, new_layer),
508        }
509    }
510
511    /// Build and wrap a service.
512    pub fn service<S>(self, service: S) -> L::Service
513    where
514        L: Layer<S>,
515    {
516        self.layer.layer(service)
517    }
518
519    /// Get the composed layer.
520    pub fn into_layer(self) -> L {
521        self.layer
522    }
523}
524
525// ============================================================================
526// Static Middleware Implementations
527// ============================================================================
528
529/// Logging middleware with static dispatch.
530#[derive(Debug, Clone)]
531pub struct LoggingLayer {
532    level: LogLevel,
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
536pub enum LogLevel {
537    Debug,
538    Info,
539    Warn,
540    Error,
541}
542
543impl LoggingLayer {
544    /// Create new logging layer.
545    pub fn new(level: LogLevel) -> Self {
546        Self { level }
547    }
548
549    /// Create info-level logger.
550    pub fn info() -> Self {
551        Self::new(LogLevel::Info)
552    }
553
554    /// Create debug-level logger.
555    pub fn debug() -> Self {
556        Self::new(LogLevel::Debug)
557    }
558}
559
560impl Default for LoggingLayer {
561    fn default() -> Self {
562        Self::info()
563    }
564}
565
566impl<S> Layer<S> for LoggingLayer {
567    type Service = LoggingService<S>;
568
569    fn layer(&self, inner: S) -> Self::Service {
570        LoggingService {
571            inner,
572            level: self.level,
573        }
574    }
575}
576
577/// Logging service wrapping inner service.
578#[derive(Debug, Clone)]
579pub struct LoggingService<S> {
580    inner: S,
581    #[allow(dead_code)]
582    level: LogLevel,
583}
584
585impl<S> Service<HttpRequest> for LoggingService<S>
586where
587    S: Service<HttpRequest, Response = HttpResponse, Error = Error> + Clone + Send + Sync + 'static,
588    S::Future: Send,
589{
590    type Response = HttpResponse;
591    type Error = Error;
592    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
593
594    fn call(&self, req: HttpRequest) -> Self::Future {
595        let inner = self.inner.clone();
596        let method = req.method.clone();
597        let path = req.path.clone();
598
599        MIDDLEWARE_STATS.record_call();
600
601        Box::pin(async move {
602            let start = std::time::Instant::now();
603            let result = inner.call(req).await;
604            let duration = start.elapsed();
605
606            match &result {
607                Ok(resp) => {
608                    tracing::info!(
609                        method = %method,
610                        path = %path,
611                        status = resp.status,
612                        duration_ms = duration.as_millis() as u64,
613                        "Request completed"
614                    );
615                }
616                Err(e) => {
617                    tracing::error!(
618                        method = %method,
619                        path = %path,
620                        error = %e,
621                        duration_ms = duration.as_millis() as u64,
622                        "Request failed"
623                    );
624                }
625            }
626
627            result
628        })
629    }
630}
631
632/// Timeout middleware with static dispatch.
633#[derive(Debug, Clone)]
634pub struct TimeoutLayer {
635    duration: std::time::Duration,
636}
637
638impl TimeoutLayer {
639    /// Create new timeout layer.
640    pub fn new(duration: std::time::Duration) -> Self {
641        Self { duration }
642    }
643
644    /// Create from seconds.
645    pub fn from_secs(secs: u64) -> Self {
646        Self::new(std::time::Duration::from_secs(secs))
647    }
648
649    /// Create from milliseconds.
650    pub fn from_millis(millis: u64) -> Self {
651        Self::new(std::time::Duration::from_millis(millis))
652    }
653}
654
655impl<S> Layer<S> for TimeoutLayer {
656    type Service = TimeoutService<S>;
657
658    fn layer(&self, inner: S) -> Self::Service {
659        TimeoutService {
660            inner,
661            duration: self.duration,
662        }
663    }
664}
665
666/// Timeout service.
667#[derive(Debug, Clone)]
668pub struct TimeoutService<S> {
669    inner: S,
670    duration: std::time::Duration,
671}
672
673impl<S> Service<HttpRequest> for TimeoutService<S>
674where
675    S: Service<HttpRequest, Response = HttpResponse, Error = Error> + Clone + Send + Sync + 'static,
676    S::Future: Send,
677{
678    type Response = HttpResponse;
679    type Error = Error;
680    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
681
682    fn call(&self, req: HttpRequest) -> Self::Future {
683        let inner = self.inner.clone();
684        let duration = self.duration;
685
686        MIDDLEWARE_STATS.record_call();
687
688        Box::pin(async move {
689            match tokio::time::timeout(duration, inner.call(req)).await {
690                Ok(result) => result,
691                Err(_) => Err(Error::timeout("Request timed out")),
692            }
693        })
694    }
695}
696
697/// Request ID middleware.
698#[derive(Debug, Clone, Default)]
699pub struct RequestIdLayer;
700
701impl<S> Layer<S> for RequestIdLayer {
702    type Service = RequestIdService<S>;
703
704    fn layer(&self, inner: S) -> Self::Service {
705        RequestIdService { inner }
706    }
707}
708
709/// Request ID service.
710#[derive(Debug, Clone)]
711pub struct RequestIdService<S> {
712    inner: S,
713}
714
715impl<S> Service<HttpRequest> for RequestIdService<S>
716where
717    S: Service<HttpRequest, Response = HttpResponse, Error = Error> + Clone + Send + Sync + 'static,
718    S::Future: Send,
719{
720    type Response = HttpResponse;
721    type Error = Error;
722    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
723
724    fn call(&self, mut req: HttpRequest) -> Self::Future {
725        let inner = self.inner.clone();
726
727        MIDDLEWARE_STATS.record_call();
728
729        // Generate request ID if not present
730        let request_id = req
731            .headers
732            .get("x-request-id")
733            .map(str::to_owned)
734            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
735
736        req.headers.insert("x-request-id", request_id.as_str());
737
738        Box::pin(async move {
739            let mut resp = inner.call(req).await?;
740            resp.headers.insert("x-request-id".to_string(), request_id);
741            Ok(resp)
742        })
743    }
744}
745
746// ============================================================================
747// Handler Service Adapter
748// ============================================================================
749
750/// Wraps a handler function as a Service.
751#[derive(Clone)]
752pub struct HandlerService<H, Args> {
753    handler: H,
754    _args: PhantomData<Args>,
755}
756
757impl<H, Args> HandlerService<H, Args> {
758    /// Create new handler service.
759    pub fn new(handler: H) -> Self {
760        Self {
761            handler,
762            _args: PhantomData,
763        }
764    }
765}
766
767/// Trait for handlers that can be converted to services.
768pub trait IntoService<Args> {
769    /// The service type.
770    type Service;
771
772    /// Convert into a service.
773    fn into_service(self) -> Self::Service;
774}
775
776// Implement for async fn() -> HttpResponse
777impl<H, Fut> IntoService<()> for H
778where
779    H: Fn() -> Fut + Clone + Send + Sync + 'static,
780    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
781{
782    type Service = HandlerService<H, ()>;
783
784    fn into_service(self) -> Self::Service {
785        HandlerService::new(self)
786    }
787}
788
789impl<H, Fut> Service<HttpRequest> for HandlerService<H, ()>
790where
791    H: Fn() -> Fut + Clone + Send + Sync + 'static,
792    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
793{
794    type Response = HttpResponse;
795    type Error = Error;
796    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
797
798    fn call(&self, _req: HttpRequest) -> Self::Future {
799        let handler = self.handler.clone();
800        Box::pin(async move { handler().await })
801    }
802}
803
804// Implement for async fn(HttpRequest) -> HttpResponse
805impl<H, Fut> IntoService<(HttpRequest,)> for H
806where
807    H: Fn(HttpRequest) -> Fut + Clone + Send + Sync + 'static,
808    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
809{
810    type Service = HandlerService<H, (HttpRequest,)>;
811
812    fn into_service(self) -> Self::Service {
813        HandlerService::new(self)
814    }
815}
816
817impl<H, Fut> Service<HttpRequest> for HandlerService<H, (HttpRequest,)>
818where
819    H: Fn(HttpRequest) -> Fut + Clone + Send + Sync + 'static,
820    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
821{
822    type Response = HttpResponse;
823    type Error = Error;
824    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
825
826    fn call(&self, req: HttpRequest) -> Self::Future {
827        let handler = self.handler.clone();
828        Box::pin(async move { handler(req).await })
829    }
830}
831
832// Implement for async fn(E1) -> HttpResponse where E1: Extract
833impl<H, Fut, E1> IntoService<(E1,)> for H
834where
835    H: Fn(E1) -> Fut + Clone + Send + Sync + 'static,
836    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
837    E1: Extract + Send + 'static,
838{
839    type Service = ExtractorHandlerService<H, (E1,)>;
840
841    fn into_service(self) -> Self::Service {
842        ExtractorHandlerService::new(self)
843    }
844}
845
846/// Handler service that extracts arguments.
847#[derive(Clone)]
848pub struct ExtractorHandlerService<H, Args> {
849    handler: H,
850    _args: PhantomData<Args>,
851}
852
853impl<H, Args> ExtractorHandlerService<H, Args> {
854    /// Create new extractor handler service.
855    pub fn new(handler: H) -> Self {
856        Self {
857            handler,
858            _args: PhantomData,
859        }
860    }
861}
862
863impl<H, Fut, E1> Service<HttpRequest> for ExtractorHandlerService<H, (E1,)>
864where
865    H: Fn(E1) -> Fut + Clone + Send + Sync + 'static,
866    Fut: Future<Output = Result<HttpResponse, Error>> + Send + 'static,
867    E1: Extract + Send + 'static,
868{
869    type Response = HttpResponse;
870    type Error = Error;
871    type Future = Pin<Box<dyn Future<Output = Result<HttpResponse, Error>> + Send>>;
872
873    fn call(&self, req: HttpRequest) -> Self::Future {
874        let handler = self.handler.clone();
875        Box::pin(async move {
876            let e1 = E1::extract(&req)?;
877            handler(e1).await
878        })
879    }
880}
881
882// ============================================================================
883// Statistics
884// ============================================================================
885
886/// Global statistics for zero-cost abstractions.
887#[derive(Debug, Default)]
888pub struct ZeroCostStats {
889    extractions: AtomicU64,
890    middleware_calls: AtomicU64,
891}
892
893impl ZeroCostStats {
894    // These take no name: the counters are single totals, so a name argument
895    // implied a per-extractor breakdown that was never recorded.
896    fn record_extraction(&self) {
897        self.extractions.fetch_add(1, Ordering::Relaxed);
898    }
899
900    fn record_call(&self) {
901        self.middleware_calls.fetch_add(1, Ordering::Relaxed);
902    }
903
904    /// Get extraction count.
905    pub fn extractions(&self) -> u64 {
906        self.extractions.load(Ordering::Relaxed)
907    }
908
909    /// Get middleware call count.
910    pub fn middleware_calls(&self) -> u64 {
911        self.middleware_calls.load(Ordering::Relaxed)
912    }
913}
914
915static EXTRACTOR_STATS: ZeroCostStats = ZeroCostStats {
916    extractions: AtomicU64::new(0),
917    middleware_calls: AtomicU64::new(0),
918};
919
920static MIDDLEWARE_STATS: ZeroCostStats = ZeroCostStats {
921    extractions: AtomicU64::new(0),
922    middleware_calls: AtomicU64::new(0),
923};
924
925/// Get extractor statistics.
926pub fn extractor_stats() -> &'static ZeroCostStats {
927    &EXTRACTOR_STATS
928}
929
930/// Get middleware statistics.
931pub fn middleware_stats() -> &'static ZeroCostStats {
932    &MIDDLEWARE_STATS
933}
934
935// ============================================================================
936// Tests
937// ============================================================================
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942    use bytes::Bytes;
943
944    fn create_request() -> HttpRequest {
945        let mut req = HttpRequest::new("GET", "/api/users/123?page=1&limit=10");
946        req.headers
947            .insert("content-type", "application/json".to_string());
948        req.headers
949            .insert("authorization", "Bearer token123".to_string());
950        req.body = Bytes::from_static(br#"{"name":"test"}"#);
951        req.push_param("id", "123");
952        req
953    }
954
955    #[test]
956    fn test_raw_body_extract() {
957        let req = create_request();
958        let body = RawBody::extract(&req).unwrap();
959        assert_eq!(body.as_ref(), br#"{"name":"test"}"#);
960    }
961
962    #[test]
963    fn test_method_extract() {
964        let req = create_request();
965        let method = Method::extract(&req).unwrap();
966        assert_eq!(&*method, "GET");
967    }
968
969    #[test]
970    fn test_path_extract() {
971        let req = create_request();
972        let path = RequestPath::extract(&req).unwrap();
973        assert_eq!(&*path, "/api/users/123");
974    }
975
976    #[test]
977    fn test_content_type_extract() {
978        let req = create_request();
979        let content_type = ContentType::extract(&req).unwrap();
980        assert!(content_type.is_json());
981    }
982
983    #[test]
984    fn test_authorization_extract() {
985        let req = create_request();
986        let auth = Authorization::extract(&req).unwrap();
987        assert_eq!(auth.bearer(), Some("token123"));
988    }
989
990    #[test]
991    fn test_path_param_extract() {
992        let req = create_request();
993        let id = PathParam::<0>::extract(&req).unwrap();
994        assert_eq!(&*id, "123");
995    }
996
997    #[test]
998    fn test_query_params_extract_reserved_chars() {
999        // Regression: values containing `&`, `=`, `%`, or `+` used to be
1000        // spliced into a query string without re-encoding, splitting into
1001        // bogus fields or mangling the value.
1002        #[derive(serde::Deserialize)]
1003        struct Query {
1004            q: String,
1005            plus: String,
1006        }
1007
1008        // Percent-encoded on the wire, so the reserved characters survive the
1009        // trip: a literal `&`/`=`/`%` in a value, and a literal `+` (which is a
1010        // space when unescaped).
1011        let req = HttpRequest::new("GET", "/search?q=a%26b%3Dc%25d&plus=1%2B1");
1012
1013        let QueryParams(query) = QueryParams::<Query>::extract(&req).unwrap();
1014        assert_eq!(query.q, "a&b=c%d");
1015        assert_eq!(query.plus, "1+1");
1016    }
1017
1018    #[test]
1019    fn test_optional_extract() {
1020        let req = create_request();
1021
1022        // Existing authorization
1023        let auth = Optional::<Authorization>::extract(&req).unwrap();
1024        assert!(auth.0.is_some());
1025
1026        // Optional content type
1027        let ct = Optional::<ContentType>::extract(&req).unwrap();
1028        assert!(ct.0.is_some());
1029    }
1030
1031    #[test]
1032    fn test_tuple_extract() {
1033        let req = create_request();
1034
1035        let (method, path) = <(Method, RequestPath)>::extract(&req).unwrap();
1036        assert_eq!(&*method, "GET");
1037        assert_eq!(&*path, "/api/users/123");
1038    }
1039
1040    #[test]
1041    fn test_two_extract() {
1042        let req = create_request();
1043
1044        let Two(method, path) = Two::<Method, RequestPath>::extract(&req).unwrap();
1045        assert_eq!(&*method, "GET");
1046        assert_eq!(&*path, "/api/users/123");
1047    }
1048
1049    #[test]
1050    fn test_layer_builder() {
1051        let builder = LayerBuilder::new()
1052            .layer(LoggingLayer::info())
1053            .layer(TimeoutLayer::from_secs(30))
1054            .layer(RequestIdLayer);
1055
1056        let _layer = builder.into_layer();
1057    }
1058
1059    #[test]
1060    fn test_identity_layer() {
1061        struct DummyService;
1062        let identity = Identity;
1063        let _service = identity.layer(DummyService);
1064    }
1065
1066    #[test]
1067    fn test_logging_layer() {
1068        let layer = LoggingLayer::new(LogLevel::Info);
1069        assert_eq!(layer.level, LogLevel::Info);
1070    }
1071
1072    #[test]
1073    fn test_timeout_layer() {
1074        let layer = TimeoutLayer::from_secs(30);
1075        assert_eq!(layer.duration, std::time::Duration::from_secs(30));
1076    }
1077
1078    #[test]
1079    fn test_stats() {
1080        let extractor = extractor_stats();
1081        let _ = extractor.extractions();
1082
1083        let middleware = middleware_stats();
1084        let _ = middleware.middleware_calls();
1085    }
1086}