mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-08 00:00:24 +02:00
Add a separate trait for optional extractors (#2475)
This commit is contained in:
@@ -24,9 +24,9 @@ pub mod multipart;
|
||||
#[cfg(feature = "scheme")]
|
||||
mod scheme;
|
||||
|
||||
pub use self::{
|
||||
cached::Cached, host::Host, optional_path::OptionalPath, with_rejection::WithRejection,
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
pub use self::optional_path::OptionalPath;
|
||||
pub use self::{cached::Cached, host::Host, with_rejection::WithRejection};
|
||||
|
||||
#[cfg(feature = "cookie")]
|
||||
pub use self::cookie::CookieJar;
|
||||
@@ -41,7 +41,10 @@ pub use self::cookie::SignedCookieJar;
|
||||
pub use self::form::{Form, FormRejection};
|
||||
|
||||
#[cfg(feature = "query")]
|
||||
pub use self::query::{OptionalQuery, OptionalQueryRejection, Query, QueryRejection};
|
||||
#[allow(deprecated)]
|
||||
pub use self::query::OptionalQuery;
|
||||
#[cfg(feature = "query")]
|
||||
pub use self::query::{OptionalQueryRejection, Query, QueryRejection};
|
||||
|
||||
#[cfg(feature = "multipart")]
|
||||
pub use self::multipart::Multipart;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::{
|
||||
extract::{path::ErrorKind, rejection::PathRejection, FromRequestParts, Path},
|
||||
extract::{rejection::PathRejection, FromRequestParts, Path},
|
||||
RequestPartsExt,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -31,9 +31,11 @@ use serde::de::DeserializeOwned;
|
||||
/// .route("/blog/{page}", get(render_blog));
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
#[deprecated = "Use Option<Path<_>> instead"]
|
||||
#[derive(Debug)]
|
||||
pub struct OptionalPath<T>(pub Option<T>);
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> FromRequestParts<S> for OptionalPath<T>
|
||||
where
|
||||
T: DeserializeOwned + Send + 'static,
|
||||
@@ -45,19 +47,15 @@ where
|
||||
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),
|
||||
}
|
||||
parts
|
||||
.extract::<Option<Path<T>>>()
|
||||
.await
|
||||
.map(|opt| Self(opt.map(|Path(x)| x)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::{
|
||||
extract::FromRequestParts,
|
||||
extract::{FromRequestParts, OptionalFromRequestParts},
|
||||
response::{IntoResponse, Response},
|
||||
Error,
|
||||
};
|
||||
@@ -18,6 +18,19 @@ use std::fmt;
|
||||
/// with the `multiple` attribute. Those values can be collected into a `Vec` or other sequential
|
||||
/// container.
|
||||
///
|
||||
/// # `Option<Query<T>>` behavior
|
||||
///
|
||||
/// If `Query<T>` itself is used as an extractor and there is no query string in
|
||||
/// the request URL, `T`'s `Deserialize` implementation is called on an empty
|
||||
/// string instead.
|
||||
///
|
||||
/// You can avoid this by using `Option<Query<T>>`, which gives you `None` in
|
||||
/// the case that there is no query string in the request URL.
|
||||
///
|
||||
/// Note that an empty query string is not the same as no query string, that is
|
||||
/// `https://example.org/` and `https://example.org/?` are not treated the same
|
||||
/// in this case.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
@@ -96,6 +109,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> OptionalFromRequestParts<S> for Query<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = QueryRejection;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
_state: &S,
|
||||
) -> Result<Option<Self>, Self::Rejection> {
|
||||
if let Some(query) = parts.uri.query() {
|
||||
let value = serde_html_form::from_str(query)
|
||||
.map_err(|err| QueryRejection::FailedToDeserializeQueryString(Error::new(err)))?;
|
||||
Ok(Some(Self(value)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
axum_core::__impl_deref!(Query);
|
||||
|
||||
/// Rejection used for [`Query`].
|
||||
@@ -182,9 +216,11 @@ impl std::error::Error for QueryRejection {
|
||||
///
|
||||
/// [example]: https://github.com/tokio-rs/axum/blob/main/examples/query-params-with-empty-strings/src/main.rs
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "query")))]
|
||||
#[deprecated = "Use Option<Query<_>> instead"]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct OptionalQuery<T>(pub Option<T>);
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> FromRequestParts<S> for OptionalQuery<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
@@ -204,6 +240,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> std::ops::Deref for OptionalQuery<T> {
|
||||
type Target = Option<T>;
|
||||
|
||||
@@ -213,6 +250,7 @@ impl<T> std::ops::Deref for OptionalQuery<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> std::ops::DerefMut for OptionalQuery<T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
@@ -260,6 +298,7 @@ impl std::error::Error for OptionalQueryRejection {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_helpers::*;
|
||||
|
||||
Reference in New Issue
Block a user