Add MatchedPath extractor (#412)

Fixes #386
This commit is contained in:
David Pedersen
2021-10-25 21:38:29 +00:00
committed by GitHub
parent e43bdf0ecf
commit 02a035fb14
7 changed files with 139 additions and 3 deletions
+86
View File
@@ -0,0 +1,86 @@
use super::{rejection::*, FromRequest, RequestParts};
use async_trait::async_trait;
use std::sync::Arc;
/// Access the path in the router that matches the request.
///
/// ```
/// use axum::{
/// Router,
/// extract::MatchedPath,
/// routing::get,
/// };
///
/// let app = Router::new().route(
/// "/users/:id",
/// get(|path: MatchedPath| async move {
/// let path = path.as_str();
/// // `path` will be "/users/:id"
/// })
/// );
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// `MatchedPath` can also be accessed from middleware via request extensions.
/// This is useful for example with [`Trace`](tower_http::trace::Trace) to
/// create a span that contains the matched path:
///
/// ```
/// use axum::{
/// Router,
/// extract::MatchedPath,
/// http::Request,
/// routing::get,
/// };
/// use tower_http::trace::TraceLayer;
///
/// let app = Router::new()
/// .route("/users/:id", get(|| async { /* ... */ }))
/// .layer(
/// TraceLayer::new_for_http().make_span_with(|req: &Request<_>| {
/// let path = if let Some(path) = req.extensions().get::<MatchedPath>() {
/// path.as_str()
/// } else {
/// req.uri().path()
/// };
/// tracing::info_span!("http-request", %path)
/// }),
/// );
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
#[derive(Clone, Debug)]
pub struct MatchedPath(pub(crate) Arc<str>);
impl MatchedPath {
/// Returns a `str` representation of the path.
pub fn as_str(&self) -> &str {
&*self.0
}
}
#[async_trait]
impl<B> FromRequest<B> for MatchedPath
where
B: Send,
{
type Rejection = MatchedPathRejection;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let extensions =
req.extensions()
.ok_or(MatchedPathRejection::ExtensionsAlreadyExtracted(
ExtensionsAlreadyExtracted,
))?;
let matched_path = extensions
.get::<Self>()
.ok_or(MatchedPathRejection::MatchedPathMissing(MatchedPathMissing))?
.clone();
Ok(matched_path)
}
}
+2
View File
@@ -172,6 +172,7 @@ pub mod ws;
mod content_length_limit;
mod extension;
mod form;
mod matched_path;
mod path;
mod query;
mod raw_query;
@@ -186,6 +187,7 @@ pub use self::{
extension::Extension,
extractor_middleware::extractor_middleware,
form::Form,
matched_path::MatchedPath,
path::Path,
query::Query,
raw_query::RawQuery,
+17
View File
@@ -273,6 +273,23 @@ composite_rejection! {
}
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "No matched path found"]
/// Rejection if no matched path could be found.
///
/// See [`MatchedPath`](super::MatchedPath) for more details.
pub struct MatchedPathMissing;
}
composite_rejection! {
/// Rejection used for [`MatchedPath`](super::MatchedPath).
pub enum MatchedPathRejection {
ExtensionsAlreadyExtracted,
MatchedPathMissing,
}
}
/// Rejection used for [`ContentLengthLimit`](super::ContentLengthLimit).
///
/// Contains one variant for each way the