mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-21 22:56:46 +01:00
Add RawPathParams
(#1713)
This commit is contained in:
parent
96b7d78a3f
commit
5b07296001
8 changed files with 159 additions and 6 deletions
|
@ -15,7 +15,7 @@ error[E0277]: the trait bound `bool: FromRequestParts<()>` is not satisfied
|
|||
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
||||
and 25 others
|
||||
and 26 others
|
||||
= note: required for `bool` to implement `FromRequest<(), Body, axum_core::extract::private::ViaParts>`
|
||||
note: required by a bound in `__axum_macros_check_handler_0_from_request_check`
|
||||
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
||||
|
|
|
@ -15,6 +15,6 @@ error[E0277]: the trait bound `String: FromRequestParts<()>` is not satisfied
|
|||
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
||||
and 25 others
|
||||
and 26 others
|
||||
= help: see issue #48214
|
||||
= help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
|
||||
|
|
|
@ -13,7 +13,7 @@ error[E0277]: the trait bound `bool: IntoResponse` is not satisfied
|
|||
(Response<()>, T1, R)
|
||||
(Response<()>, T1, T2, R)
|
||||
(Response<()>, T1, T2, T3, R)
|
||||
and 122 others
|
||||
and 124 others
|
||||
note: required by a bound in `__axum_macros_check_handler_into_response::{closure#0}::check`
|
||||
--> tests/debug_handler/fail/wrong_return_type.rs:4:23
|
||||
|
|
||||
|
|
|
@ -15,4 +15,4 @@ error[E0277]: the trait bound `String: FromRequestParts<S>` is not satisfied
|
|||
<(T1, T2, T3, T4, T5, T6) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequestParts<S>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequestParts<S>>
|
||||
and 26 others
|
||||
and 27 others
|
||||
|
|
|
@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- **added:** Implement `IntoResponse` for `&'static [u8; N]` and `[u8; N]` ([#1690])
|
||||
- **fixed:** Make `Path` support types uses `serde::Deserializer::deserialize_any` ([#1693])
|
||||
- **added:** Add `RawPathParams` ([#1713])
|
||||
- **added:** Implement `Clone` and `Service` for `axum::middleware::Next` ([#1712])
|
||||
- **fixed:** Document required tokio features to run "Hello, World!" example ([#1715])
|
||||
|
||||
[#1690]: https://github.com/tokio-rs/axum/pull/1690
|
||||
[#1693]: https://github.com/tokio-rs/axum/pull/1693
|
||||
[#1712]: https://github.com/tokio-rs/axum/pull/1712
|
||||
[#1713]: https://github.com/tokio-rs/axum/pull/1713
|
||||
[#1715]: https://github.com/tokio-rs/axum/pull/1715
|
||||
|
||||
# 0.6.2 (9. January, 2023)
|
||||
|
|
|
@ -26,7 +26,7 @@ pub use axum_macros::{FromRef, FromRequest, FromRequestParts};
|
|||
#[allow(deprecated)]
|
||||
pub use self::{
|
||||
host::Host,
|
||||
path::Path,
|
||||
path::{Path, RawPathParams},
|
||||
raw_form::RawForm,
|
||||
raw_query::RawQuery,
|
||||
request_parts::{BodyStream, RawBody},
|
||||
|
|
|
@ -6,6 +6,7 @@ mod de;
|
|||
use crate::{
|
||||
extract::{rejection::*, FromRequestParts},
|
||||
routing::url_params::UrlParams,
|
||||
util::PercentDecodedStr,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
|
@ -14,6 +15,7 @@ use serde::de::DeserializeOwned;
|
|||
use std::{
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Extractor that will get captures from the URL and parse them using
|
||||
|
@ -426,6 +428,125 @@ impl fmt::Display for FailedToDeserializePathParams {
|
|||
|
||||
impl std::error::Error for FailedToDeserializePathParams {}
|
||||
|
||||
/// Extractor that will get captures from the URL without deserializing them.
|
||||
///
|
||||
/// In general you should prefer to use [`Path`] as it is higher level, however `RawPathParams` is
|
||||
/// suitable if just want the raw params without deserializing them and thus saving some
|
||||
/// allocations.
|
||||
///
|
||||
/// Any percent encoded parameters will be automatically decoded. The decoded parameters must be
|
||||
/// valid UTF-8, otherwise `RawPathParams` will fail and return a `400 Bad Request` response.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use axum::{
|
||||
/// extract::RawPathParams,
|
||||
/// routing::get,
|
||||
/// Router,
|
||||
/// };
|
||||
///
|
||||
/// async fn users_teams_show(params: RawPathParams) {
|
||||
/// for (key, value) in ¶ms {
|
||||
/// println!("{key:?} = {value:?}");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct RawPathParams(Vec<(Arc<str>, PercentDecodedStr)>);
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for RawPathParams
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = RawPathParamsRejection;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
let params = match parts.extensions.get::<UrlParams>() {
|
||||
Some(UrlParams::Params(params)) => params,
|
||||
Some(UrlParams::InvalidUtf8InPathParam { key }) => {
|
||||
return Err(InvalidUtf8InPathParam {
|
||||
key: Arc::clone(key),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
None => {
|
||||
return Err(MissingPathParams.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self(params.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl RawPathParams {
|
||||
/// Get an iterator over the path parameters.
|
||||
pub fn iter(&self) -> RawPathParamsIter<'_> {
|
||||
self.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a RawPathParams {
|
||||
type Item = (&'a str, &'a str);
|
||||
type IntoIter = RawPathParamsIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
RawPathParamsIter(self.0.iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over raw path parameters.
|
||||
///
|
||||
/// Created with [`RawPathParams::iter`].
|
||||
#[derive(Debug)]
|
||||
pub struct RawPathParamsIter<'a>(std::slice::Iter<'a, (Arc<str>, PercentDecodedStr)>);
|
||||
|
||||
impl<'a> Iterator for RawPathParamsIter<'a> {
|
||||
type Item = (&'a str, &'a str);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (key, value) = self.0.next()?;
|
||||
Some((&**key, value.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejection used by [`RawPathParams`] if a parameter contained text that, once percent decoded,
|
||||
/// wasn't valid UTF-8.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidUtf8InPathParam {
|
||||
key: Arc<str>,
|
||||
}
|
||||
|
||||
impl InvalidUtf8InPathParam {
|
||||
/// Get the response body text used for this rejection.
|
||||
pub fn body_text(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
|
||||
/// Get the status code used for this rejection.
|
||||
pub fn status(&self) -> StatusCode {
|
||||
StatusCode::BAD_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidUtf8InPathParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Invalid UTF-8 in `{}`", self.key)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidUtf8InPathParam {}
|
||||
|
||||
impl IntoResponse for InvalidUtf8InPathParam {
|
||||
fn into_response(self) -> Response {
|
||||
(self.status(), self.body_text()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -710,4 +831,23 @@ mod tests {
|
|||
.await
|
||||
.starts_with("Wrong number of path arguments for `Path`. Expected 1 but got 2"));
|
||||
}
|
||||
|
||||
#[crate::test]
|
||||
async fn raw_path_params() {
|
||||
let app = Router::new().route(
|
||||
"/:a/:b/:c",
|
||||
get(|params: RawPathParams| async move {
|
||||
params
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let res = client.get("/foo/bar/baz").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "a=foo b=bar c=baz");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Rejection response types.
|
||||
|
||||
pub use crate::extract::path::FailedToDeserializePathParams;
|
||||
pub use crate::extract::path::{FailedToDeserializePathParams, InvalidUtf8InPathParam};
|
||||
pub use axum_core::extract::rejection::*;
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
|
@ -155,6 +155,17 @@ composite_rejection! {
|
|||
}
|
||||
}
|
||||
|
||||
composite_rejection! {
|
||||
/// Rejection used for [`RawPathParams`](super::RawPathParams).
|
||||
///
|
||||
/// Contains one variant for each way the [`RawPathParams`](super::RawPathParams) extractor
|
||||
/// can fail.
|
||||
pub enum RawPathParamsRejection {
|
||||
InvalidUtf8InPathParam,
|
||||
MissingPathParams,
|
||||
}
|
||||
}
|
||||
|
||||
composite_rejection! {
|
||||
/// Rejection used for [`Host`](super::Host).
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue