From b4bdddf9d21b4005fd91d5903eea41e36c6527ca Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 8 Aug 2021 14:45:31 +0200 Subject: [PATCH] Add `NestedUri` (#161) Fixes https://github.com/tokio-rs/axum/issues/159 --- CHANGELOG.md | 1 + src/extract/mod.rs | 1 + src/extract/rejection.rs | 10 ++++++++ src/extract/request_parts.rs | 44 +++++++++++++++++++++++++++++++++++- src/routing.rs | 7 +++++- src/tests/nest.rs | 25 ++++++++++++++++++++ 6 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b19c628..2f3a26d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `std::error::Error` for all rejections ([#153](https://github.com/tokio-rs/axum/pull/153)) - Add `RoutingDsl::or` for combining routes ([#108](https://github.com/tokio-rs/axum/pull/108)) - Add `handle_error` to `service::OnMethod` ([#160](https://github.com/tokio-rs/axum/pull/160)) +- Add `NestedUri` for extracting request URI in nested services ([#161](https://github.com/tokio-rs/axum/pull/161)) ## Breaking changes diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 33c63639..faa4ddbb 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -278,6 +278,7 @@ pub use self::{ path::Path, query::Query, raw_query::RawQuery, + request_parts::NestedUri, request_parts::{Body, BodyStream}, }; #[doc(no_inline)] diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs index 7bde7421..834a0843 100644 --- a/src/extract/rejection.rs +++ b/src/extract/rejection.rs @@ -108,6 +108,16 @@ define_rejection! { pub struct InvalidFormContentType; } +define_rejection! { + #[status = INTERNAL_SERVER_ERROR] + #[body = "`NestedUri` extractor used for route that isn't nested"] + /// Rejection type used if you try and extract [`NestedUri`] from a route that + /// isn't nested. + /// + /// [`NestedUri`]: crate::extract::NestedUri + pub struct NotNested; +} + /// Rejection type for [`Path`](super::Path) if the capture route /// param didn't have the expected type. #[derive(Debug)] diff --git a/src/extract/request_parts.rs b/src/extract/request_parts.rs index a6c3f46d..e79a7e86 100644 --- a/src/extract/request_parts.rs +++ b/src/extract/request_parts.rs @@ -1,4 +1,4 @@ -use super::{rejection::*, take_body, FromRequest, RequestParts}; +use super::{rejection::*, take_body, Extension, FromRequest, RequestParts}; use async_trait::async_trait; use bytes::Bytes; use futures_util::stream::Stream; @@ -74,6 +74,48 @@ where } } +/// Extractor that gets the request URI for a nested service. +/// +/// This is necessary since [`Uri`](http::Uri), when used as an extractor, will +/// always be the full URI. +/// +/// # Example +/// +/// ``` +/// use axum::{prelude::*, extract::NestedUri, http::Uri}; +/// +/// let api_routes = route( +/// "/users", +/// get(|uri: Uri, NestedUri(nested_uri): NestedUri| async { +/// // `uri` is `/api/users` +/// // `nested_uri` is `/users` +/// }), +/// ); +/// +/// let app = nest("/api", api_routes); +/// # async { +/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); +/// # }; +/// ``` +#[derive(Debug, Clone)] +pub struct NestedUri(pub Uri); + +#[async_trait] +impl FromRequest for NestedUri +where + B: Send, +{ + type Rejection = NotNested; + + async fn from_request(req: &mut RequestParts) -> Result { + let uri = Extension::::from_request(req) + .await + .map_err(|_| NotNested)? + .0; + Ok(uri) + } +} + #[async_trait] impl FromRequest for Version where diff --git a/src/routing.rs b/src/routing.rs index 54d42b12..c4c16209 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -4,7 +4,10 @@ use self::future::{BoxRouteFuture, EmptyRouterFuture, NestedFuture, RouteFuture} use crate::{ body::{box_body, BoxBody}, buffer::MpscBuffer, - extract::connect_info::{Connected, IntoMakeServiceWithConnectInfo}, + extract::{ + connect_info::{Connected, IntoMakeServiceWithConnectInfo}, + NestedUri, + }, response::IntoResponse, service::{HandleError, HandleErrorFromRouter}, util::ByteStr, @@ -868,6 +871,8 @@ where let f = if let Some((prefix, captures)) = self.pattern.prefix_match(req.uri().path()) { let without_prefix = strip_prefix(req.uri(), prefix); + req.extensions_mut() + .insert(NestedUri(without_prefix.clone())); *req.uri_mut() = without_prefix; insert_url_params(&mut req, captures); diff --git a/src/tests/nest.rs b/src/tests/nest.rs index cee5f1d5..bf2dea24 100644 --- a/src/tests/nest.rs +++ b/src/tests/nest.rs @@ -158,3 +158,28 @@ async fn nested_url_extractor() { .unwrap(); assert_eq!(res.text().await.unwrap(), "/foo/bar/qux"); } + +#[tokio::test] +async fn nested_url_nested_extractor() { + let app = nest( + "/foo", + nest( + "/bar", + route( + "/baz", + get(|uri: extract::NestedUri| async move { uri.0.to_string() }), + ), + ), + ); + + let addr = run_in_background(app).await; + + let client = reqwest::Client::new(); + + let res = client + .get(format!("http://{}/foo/bar/baz", addr)) + .send() + .await + .unwrap(); + assert_eq!(res.text().await.unwrap(), "/baz"); +}