Coverage Report

Created: 2026-07-13 08:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/axum-0.8.9/src/routing/route.rs
Line
Count
Source
1
use crate::{
2
    body::{Body, HttpBody},
3
    response::Response,
4
    util::MapIntoResponse,
5
};
6
use axum_core::{extract::Request, response::IntoResponse};
7
use bytes::Bytes;
8
use http::{
9
    header::{self, CONTENT_LENGTH},
10
    HeaderMap, HeaderValue, Method,
11
};
12
use pin_project_lite::pin_project;
13
use std::{
14
    convert::Infallible,
15
    fmt,
16
    future::Future,
17
    pin::Pin,
18
    task::{ready, Context, Poll},
19
};
20
use tower::{
21
    util::{BoxCloneSyncService, MapErrLayer, Oneshot},
22
    ServiceExt,
23
};
24
use tower_layer::Layer;
25
use tower_service::Service;
26
27
/// How routes are stored inside a [`Router`](super::Router).
28
///
29
/// You normally shouldn't need to care about this type. It's used in
30
/// [`Router::layer`](super::Router::layer).
31
pub struct Route<E = Infallible>(BoxCloneSyncService<Request, Response, E>);
32
33
impl<E> Route<E> {
34
0
    pub(crate) fn new<T>(svc: T) -> Self
35
0
    where
36
0
        T: Service<Request, Error = E> + Clone + Send + Sync + 'static,
37
0
        T::Response: IntoResponse + 'static,
38
0
        T::Future: Send + 'static,
39
    {
40
0
        Self(BoxCloneSyncService::new(MapIntoResponse::new(svc)))
41
0
    }
Unexecuted instantiation: <axum::routing::route::Route>::new::<axum::handler::service::HandlerService<tonic::service::router::unimplemented, ((),), ()>>
Unexecuted instantiation: <axum::routing::route::Route>::new::<tower::util::service_fn::ServiceFn<<axum::routing::method_routing::MethodRouter>::new::{closure#0}>>
Unexecuted instantiation: <axum::routing::route::Route>::new::<axum::routing::not_found::NotFound>
Unexecuted instantiation: <axum::routing::route::Route<_>>::new::<_>
42
43
    /// Variant of [`Route::call`] that takes ownership of the route to avoid cloning.
44
0
    pub(crate) fn call_owned(self, req: Request<Body>) -> RouteFuture<E> {
45
0
        let req = req.map(Body::new);
46
0
        self.oneshot_inner_owned(req).not_top_level()
47
0
    }
48
49
0
    pub(crate) fn oneshot_inner(&mut self, req: Request) -> RouteFuture<E> {
50
0
        let method = req.method().clone();
51
0
        RouteFuture::new(method, self.0.clone().oneshot(req))
52
0
    }
Unexecuted instantiation: <axum::routing::route::Route>::oneshot_inner
Unexecuted instantiation: <axum::routing::route::Route<_>>::oneshot_inner
53
54
    /// Variant of [`Route::oneshot_inner`] that takes ownership of the route to avoid cloning.
55
0
    pub(crate) fn oneshot_inner_owned(self, req: Request) -> RouteFuture<E> {
56
0
        let method = req.method().clone();
57
0
        RouteFuture::new(method, self.0.oneshot(req))
58
0
    }
59
60
0
    pub(crate) fn layer<L, NewError>(self, layer: L) -> Route<NewError>
61
0
    where
62
0
        L: Layer<Route<E>> + Clone + Send + 'static,
63
0
        L::Service: Service<Request> + Clone + Send + Sync + 'static,
64
0
        <L::Service as Service<Request>>::Response: IntoResponse + 'static,
65
0
        <L::Service as Service<Request>>::Error: Into<NewError> + 'static,
66
0
        <L::Service as Service<Request>>::Future: Send + 'static,
67
0
        NewError: 'static,
68
    {
69
0
        let layer = (MapErrLayer::new(Into::into), layer);
70
71
0
        Route::new(layer.layer(self))
72
0
    }
73
}
74
75
impl<E> Clone for Route<E> {
76
    #[track_caller]
77
0
    fn clone(&self) -> Self {
78
0
        Self(self.0.clone())
79
0
    }
80
}
81
82
impl<E> fmt::Debug for Route<E> {
83
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84
0
        f.debug_struct("Route").finish()
85
0
    }
86
}
87
88
impl<B, E> Service<Request<B>> for Route<E>
89
where
90
    B: HttpBody<Data = bytes::Bytes> + Send + 'static,
91
    B::Error: Into<axum_core::BoxError>,
92
{
93
    type Response = Response;
94
    type Error = E;
95
    type Future = RouteFuture<E>;
96
97
    #[inline]
98
0
    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
99
0
        Poll::Ready(Ok(()))
100
0
    }
101
102
    #[inline]
103
0
    fn call(&mut self, req: Request<B>) -> Self::Future {
104
0
        self.oneshot_inner(req.map(Body::new)).not_top_level()
105
0
    }
Unexecuted instantiation: <axum::routing::route::Route as tower_service::Service<http::request::Request<axum_core::body::Body>>>::call
Unexecuted instantiation: <axum::routing::route::Route<_> as tower_service::Service<http::request::Request<_>>>::call
106
}
107
108
pin_project! {
109
    /// Response future for [`Route`].
110
    pub struct RouteFuture<E> {
111
        #[pin]
112
        inner: Oneshot<BoxCloneSyncService<Request, Response, E>, Request>,
113
        method: Method,
114
        allow_header: Option<Bytes>,
115
        top_level: bool,
116
    }
117
}
118
119
impl<E> RouteFuture<E> {
120
0
    fn new(
121
0
        method: Method,
122
0
        inner: Oneshot<BoxCloneSyncService<Request, Response, E>, Request>,
123
0
    ) -> Self {
124
0
        Self {
125
0
            inner,
126
0
            method,
127
0
            allow_header: None,
128
0
            top_level: true,
129
0
        }
130
0
    }
Unexecuted instantiation: <axum::routing::route::RouteFuture<core::convert::Infallible>>::new
Unexecuted instantiation: <axum::routing::route::RouteFuture<_>>::new
131
132
0
    pub(crate) fn allow_header(mut self, allow_header: Bytes) -> Self {
133
0
        self.allow_header = Some(allow_header);
134
0
        self
135
0
    }
136
137
0
    pub(crate) fn not_top_level(mut self) -> Self {
138
0
        self.top_level = false;
139
0
        self
140
0
    }
Unexecuted instantiation: <axum::routing::route::RouteFuture<core::convert::Infallible>>::not_top_level
Unexecuted instantiation: <axum::routing::route::RouteFuture<_>>::not_top_level
141
}
142
143
impl<E> Future for RouteFuture<E> {
144
    type Output = Result<Response, E>;
145
146
    #[inline]
147
0
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
148
0
        let this = self.project();
149
0
        let mut res = ready!(this.inner.poll(cx))?;
150
151
0
        if *this.method == Method::CONNECT && res.status().is_success() {
152
            // From https://httpwg.org/specs/rfc9110.html#CONNECT:
153
            // > A server MUST NOT send any Transfer-Encoding or
154
            // > Content-Length header fields in a 2xx (Successful)
155
            // > response to CONNECT.
156
0
            if res.headers().contains_key(&CONTENT_LENGTH)
157
0
                || res.headers().contains_key(&header::TRANSFER_ENCODING)
158
0
                || res.size_hint().lower() != 0
159
            {
160
                error!("response to CONNECT with nonempty body");
161
0
                res = res.map(|_| Body::empty());
162
0
            }
163
0
        } else if *this.top_level {
164
0
            set_allow_header(res.headers_mut(), this.allow_header);
165
166
            // make sure to set content-length before removing the body
167
0
            set_content_length(res.size_hint(), res.headers_mut());
168
169
0
            if *this.method == Method::HEAD {
170
0
                *res.body_mut() = Body::empty();
171
0
            }
172
0
        }
173
174
0
        Poll::Ready(Ok(res))
175
0
    }
176
}
177
178
0
fn set_allow_header(headers: &mut HeaderMap, allow_header: &mut Option<Bytes>) {
179
0
    match allow_header.take() {
180
0
        Some(allow_header) if !headers.contains_key(header::ALLOW) => {
181
0
            headers.insert(
182
0
                header::ALLOW,
183
0
                HeaderValue::from_maybe_shared(allow_header).expect("invalid `Allow` header"),
184
0
            );
185
0
        }
186
0
        _ => {}
187
    }
188
0
}
189
190
0
fn set_content_length(size_hint: http_body::SizeHint, headers: &mut HeaderMap) {
191
0
    if headers.contains_key(CONTENT_LENGTH) {
192
0
        return;
193
0
    }
194
195
0
    if let Some(size) = size_hint.exact() {
196
0
        let header_value = if size == 0 {
197
            #[allow(clippy::declare_interior_mutable_const)]
198
            const ZERO: HeaderValue = HeaderValue::from_static("0");
199
200
0
            ZERO
201
        } else {
202
0
            let mut buffer = itoa::Buffer::new();
203
0
            HeaderValue::from_str(buffer.format(size)).unwrap()
204
        };
205
206
0
        headers.insert(CONTENT_LENGTH, header_value);
207
0
    }
208
0
}
209
210
pin_project! {
211
    /// A [`RouteFuture`] that always yields a [`Response`].
212
    pub struct InfallibleRouteFuture {
213
        #[pin]
214
        future: RouteFuture<Infallible>,
215
    }
216
}
217
218
impl InfallibleRouteFuture {
219
0
    pub(crate) fn new(future: RouteFuture<Infallible>) -> Self {
220
0
        Self { future }
221
0
    }
222
}
223
224
impl Future for InfallibleRouteFuture {
225
    type Output = Response;
226
227
0
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
228
0
        match ready!(self.project().future.poll(cx)) {
229
0
            Ok(response) => Poll::Ready(response),
230
            Err(err) => match err {},
231
        }
232
0
    }
233
}
234
235
#[cfg(test)]
236
mod tests {
237
    use super::*;
238
239
    #[test]
240
    fn traits() {
241
        use crate::test_helpers::*;
242
        assert_send::<Route<()>>();
243
    }
244
}