axum_extra/extract/
optional_path.rs

1use axum::{
2    extract::{rejection::PathRejection, FromRequestParts, Path},
3    RequestPartsExt,
4};
5use serde::de::DeserializeOwned;
6
7/// Extractor that extracts path arguments the same way as [`Path`], except if there aren't any.
8///
9/// This extractor can be used in place of `Path` when you have two routes that you want to handle
10/// in mostly the same way, where one has a path parameter and the other one doesn't.
11///
12/// # Example
13///
14/// ```
15/// use std::num::NonZeroU32;
16/// use axum::{
17///     response::IntoResponse,
18///     routing::get,
19///     Router,
20/// };
21/// use axum_extra::extract::OptionalPath;
22///
23/// async fn render_blog(OptionalPath(page): OptionalPath<NonZeroU32>) -> impl IntoResponse {
24///     // Convert to u32, default to page 1 if not specified
25///     let page = page.map_or(1, |param| param.get());
26///     // ...
27/// }
28///
29/// let app = Router::new()
30///     .route("/blog", get(render_blog))
31///     .route("/blog/{page}", get(render_blog));
32/// # let app: Router = app;
33/// ```
34#[deprecated = "Use Option<Path<_>> instead"]
35#[derive(Debug)]
36pub struct OptionalPath<T>(pub Option<T>);
37
38#[allow(deprecated)]
39impl<T, S> FromRequestParts<S> for OptionalPath<T>
40where
41    T: DeserializeOwned + Send + 'static,
42    S: Send + Sync,
43{
44    type Rejection = PathRejection;
45
46    async fn from_request_parts(
47        parts: &mut http::request::Parts,
48        _: &S,
49    ) -> Result<Self, Self::Rejection> {
50        parts
51            .extract::<Option<Path<T>>>()
52            .await
53            .map(|opt| Self(opt.map(|Path(x)| x)))
54    }
55}
56
57#[cfg(test)]
58#[allow(deprecated)]
59mod tests {
60    use std::num::NonZeroU32;
61
62    use axum::{routing::get, Router};
63
64    use super::OptionalPath;
65    use crate::test_helpers::TestClient;
66
67    #[crate::test]
68    async fn supports_128_bit_numbers() {
69        async fn handle(OptionalPath(param): OptionalPath<NonZeroU32>) -> String {
70            let num = param.map_or(0, |p| p.get());
71            format!("Success: {num}")
72        }
73
74        let app = Router::new()
75            .route("/", get(handle))
76            .route("/{num}", get(handle));
77
78        let client = TestClient::new(app);
79
80        let res = client.get("/").await;
81        assert_eq!(res.text().await, "Success: 0");
82
83        let res = client.get("/1").await;
84        assert_eq!(res.text().await, "Success: 1");
85
86        let res = client.get("/0").await;
87        assert_eq!(
88            res.text().await,
89            "Invalid URL: invalid value: integer `0`, expected a nonzero u32"
90        );
91
92        let res = client.get("/NaN").await;
93        assert_eq!(
94            res.text().await,
95            "Invalid URL: Cannot parse `NaN` to a `u32`"
96        );
97    }
98}