mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-29 00:00:18 +02:00
Add OptionalPath extractor (#1889)
Co-authored-by: David Pedersen <[email protected]>
This commit is contained in:
co-authored by
David Pedersen
parent
946d8c3253
commit
43b2d52403
@@ -1,6 +1,8 @@
|
||||
//! Additional extractors.
|
||||
|
||||
mod cached;
|
||||
mod optional_path;
|
||||
mod with_rejection;
|
||||
|
||||
#[cfg(feature = "form")]
|
||||
mod form;
|
||||
@@ -14,9 +16,7 @@ mod query;
|
||||
#[cfg(feature = "multipart")]
|
||||
pub mod multipart;
|
||||
|
||||
mod with_rejection;
|
||||
|
||||
pub use self::cached::Cached;
|
||||
pub use self::{cached::Cached, optional_path::OptionalPath, with_rejection::WithRejection};
|
||||
|
||||
#[cfg(feature = "cookie")]
|
||||
pub use self::cookie::CookieJar;
|
||||
@@ -39,5 +39,3 @@ pub use self::multipart::Multipart;
|
||||
#[cfg(feature = "json-lines")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::json_lines::JsonLines;
|
||||
|
||||
pub use self::with_rejection::WithRejection;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{path::ErrorKind, rejection::PathRejection, FromRequestParts, Path},
|
||||
RequestPartsExt,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
/// Extractor that extracts path arguments the same way as [`Path`], except if there aren't any.
|
||||
///
|
||||
/// This extractor can be used in place of `Path` when you have two routes that you want to handle
|
||||
/// in mostly the same way, where one has a path parameter and the other one doesn't.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::num::NonZeroU32;
|
||||
/// use axum::{
|
||||
/// response::IntoResponse,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use axum_extra::extract::OptionalPath;
|
||||
///
|
||||
/// async fn render_blog(OptionalPath(page): OptionalPath<NonZeroU32>) -> impl IntoResponse {
|
||||
/// // Convert to u32, default to page 1 if not specified
|
||||
/// let page = page.map_or(1, |param| param.get());
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/blog", get(render_blog))
|
||||
/// .route("/blog/:page", get(render_blog));
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct OptionalPath<T>(pub Option<T>);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, S> FromRequestParts<S> for OptionalPath<T>
|
||||
where
|
||||
T: DeserializeOwned + Send + 'static,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = PathRejection;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut http::request::Parts,
|
||||
_: &S,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
match parts.extract::<Path<T>>().await {
|
||||
Ok(Path(params)) => Ok(Self(Some(params))),
|
||||
Err(PathRejection::FailedToDeserializePathParams(e))
|
||||
if matches!(e.kind(), ErrorKind::WrongNumberOfParameters { got: 0, .. }) =>
|
||||
{
|
||||
Ok(Self(None))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
use super::OptionalPath;
|
||||
use crate::test_helpers::TestClient;
|
||||
|
||||
#[crate::test]
|
||||
async fn supports_128_bit_numbers() {
|
||||
async fn handle(OptionalPath(param): OptionalPath<NonZeroU32>) -> String {
|
||||
let num = param.map_or(0, |p| p.get());
|
||||
format!("Success: {num}")
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(handle))
|
||||
.route("/:num", get(handle));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
assert_eq!(res.text().await, "Success: 0");
|
||||
|
||||
let res = client.get("/1").send().await;
|
||||
assert_eq!(res.text().await, "Success: 1");
|
||||
|
||||
let res = client.get("/0").send().await;
|
||||
assert_eq!(
|
||||
res.text().await,
|
||||
"Invalid URL: invalid value: integer `0`, expected a nonzero u32"
|
||||
);
|
||||
|
||||
let res = client.get("/NaN").send().await;
|
||||
assert_eq!(
|
||||
res.text().await,
|
||||
"Invalid URL: Cannot parse `\"NaN\"` to a `u32`"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user