mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-27 00:00:24 +02:00
Add type safe state extractor (#1155)
* begin threading the state through * Pass state to extractors * make state extractor work * make sure nesting with different states work * impl Service for MethodRouter<()> * Fix some of axum-macro's tests * Implement more traits for `State` * Update examples to use `State` * consistent naming of request body param * swap type params * Default the state param to () * fix docs references * Docs and handler state refactoring * docs clean ups * more consistent naming * when does MethodRouter implement Service? * add missing docs * use `Router`'s default state type param * changelog * don't use default type param for FromRequest and RequestParts probably safer for library authors so you don't accidentally forget * fix examples * minor docs tweaks * clarify how to convert handlers into services * group methods in one impl block * make sure merged `MethodRouter`s can access state * fix docs link * test merge with same state type * Document how to access state from middleware * Port cookie extractors to use state to extract keys (#1250) * Updates ECOSYSTEM with a new sample project (#1252) * Avoid unhelpful compiler suggestion (#1251) * fix docs typo * document how library authors should access state * Add `RequestParts::with_state` * fix example * apply suggestions from review * add relevant changes to axum-extra and axum-core changelogs * Add `route_service_with_tsr` * fix trybuild expectations * make sure `SpaRouter` works with routers that have state * Change order of type params on FromRequest and RequestParts * reverse order of `RequestParts::with_state` args to match type params * Add `FromRef` trait (#1268) * Add `FromRef` trait * Remove unnecessary type params * format * fix docs link * format examples * Avoid unnecessary `MethodRouter` * apply suggestions from review Co-authored-by: Dani Pardo <[email protected]> Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
co-authored by
Dani Pardo
Jonas Platte
parent
90dbd52ee4
commit
423308de3c
@@ -190,15 +190,16 @@ macro_rules! impl_traits_for_either {
|
||||
$last:ident $(,)?
|
||||
) => {
|
||||
#[async_trait]
|
||||
impl<B, $($ident),*, $last> FromRequest<B> for $either<$($ident),*, $last>
|
||||
impl<S, B, $($ident),*, $last> FromRequest<S, B> for $either<$($ident),*, $last>
|
||||
where
|
||||
$($ident: FromRequest<B>),*,
|
||||
$last: FromRequest<B>,
|
||||
$($ident: FromRequest<S, B>),*,
|
||||
$last: FromRequest<S, B>,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = $last::Rejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
$(
|
||||
if let Ok(value) = req.extract().await {
|
||||
return Ok(Self::$ident(value));
|
||||
|
||||
@@ -30,13 +30,14 @@ use std::ops::{Deref, DerefMut};
|
||||
/// struct Session { /* ... */ }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for Session
|
||||
/// impl<S, B> FromRequest<S, B> for Session
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = (StatusCode, String);
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // load session...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
@@ -45,13 +46,14 @@ use std::ops::{Deref, DerefMut};
|
||||
/// struct CurrentUser { /* ... */ }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for CurrentUser
|
||||
/// impl<S, B> FromRequest<S, B> for CurrentUser
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = Response;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // loading a `CurrentUser` requires first loading the `Session`
|
||||
/// //
|
||||
/// // by using `Cached<Session>` we avoid extracting the session more than
|
||||
@@ -88,14 +90,15 @@ pub struct Cached<T>(pub T);
|
||||
struct CachedEntry<T>(T);
|
||||
|
||||
#[async_trait]
|
||||
impl<B, T> FromRequest<B> for Cached<T>
|
||||
impl<S, B, T> FromRequest<S, B> for Cached<T>
|
||||
where
|
||||
B: Send,
|
||||
T: FromRequest<B> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
T: FromRequest<S, B> + Clone + Send + Sync + 'static,
|
||||
{
|
||||
type Rejection = T::Rejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
match Extension::<CachedEntry<T>>::from_request(req).await {
|
||||
Ok(Extension(CachedEntry(value))) => Ok(Self(value)),
|
||||
Err(_) => {
|
||||
@@ -139,13 +142,14 @@ mod tests {
|
||||
struct Extractor(Instant);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Extractor
|
||||
impl<S, B> FromRequest<S, B> for Extractor
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(Self(Instant::now()))
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ pub use cookie::Key;
|
||||
/// let app = Router::new()
|
||||
/// .route("/sessions", post(create_session))
|
||||
/// .route("/me", get(me));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CookieJar {
|
||||
@@ -88,13 +88,14 @@ pub struct CookieJar {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for CookieJar
|
||||
impl<S, B> FromRequest<S, B> for CookieJar
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(Self::from_headers(req.headers()))
|
||||
}
|
||||
}
|
||||
@@ -226,7 +227,7 @@ fn set_cookies(jar: cookie::CookieJar, headers: &mut HeaderMap) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use axum::{body::Body, http::Request, routing::get, Extension, Router};
|
||||
use axum::{body::Body, extract::FromRef, http::Request, routing::get, Router};
|
||||
use tower::ServiceExt;
|
||||
|
||||
macro_rules! cookie_test {
|
||||
@@ -245,12 +246,15 @@ mod tests {
|
||||
jar.remove(Cookie::named("key"))
|
||||
}
|
||||
|
||||
let app = Router::<Body>::new()
|
||||
let state = AppState {
|
||||
key: Key::generate(),
|
||||
custom_key: CustomKey(Key::generate()),
|
||||
};
|
||||
|
||||
let app = Router::<_, Body>::with_state(state)
|
||||
.route("/set", get(set_cookie))
|
||||
.route("/get", get(get_cookie))
|
||||
.route("/remove", get(remove_cookie))
|
||||
.layer(Extension(Key::generate()))
|
||||
.layer(Extension(CustomKey(Key::generate())));
|
||||
.route("/remove", get(remove_cookie));
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
@@ -298,6 +302,24 @@ mod tests {
|
||||
cookie_test!(private_cookies, PrivateCookieJar);
|
||||
cookie_test!(private_cookies_with_custom_key, PrivateCookieJar<CustomKey>);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
key: Key,
|
||||
custom_key: CustomKey,
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for Key {
|
||||
fn from_ref(state: &AppState) -> Key {
|
||||
state.key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for CustomKey {
|
||||
fn from_ref(state: &AppState) -> CustomKey {
|
||||
state.custom_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CustomKey(Key);
|
||||
|
||||
@@ -313,9 +335,12 @@ mod tests {
|
||||
format!("{:?}", jar.get("key"))
|
||||
}
|
||||
|
||||
let app = Router::<Body>::new()
|
||||
.route("/get", get(get_cookie))
|
||||
.layer(Extension(Key::generate()));
|
||||
let state = AppState {
|
||||
key: Key::generate(),
|
||||
custom_key: CustomKey(Key::generate()),
|
||||
};
|
||||
|
||||
let app = Router::<_, Body>::with_state(state).route("/get", get(get_cookie));
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::{cookies_from_request, set_cookies, Cookie, Key};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
extract::{FromRef, FromRequest, RequestParts},
|
||||
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
|
||||
Extension,
|
||||
};
|
||||
use cookie::PrivateJar;
|
||||
use http::HeaderMap;
|
||||
@@ -23,9 +22,8 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// Extension,
|
||||
/// routing::{post, get},
|
||||
/// extract::TypedHeader,
|
||||
/// extract::{TypedHeader, FromRef},
|
||||
/// response::{IntoResponse, Redirect},
|
||||
/// headers::authorization::{Authorization, Bearer},
|
||||
/// http::StatusCode,
|
||||
@@ -45,22 +43,36 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// let key = Key::generate();
|
||||
/// // our application state
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {
|
||||
/// // that holds the key used to sign cookies
|
||||
/// key: Key,
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this impl tells `SignedCookieJar` how to access the key from our state
|
||||
/// impl FromRef<AppState> for Key {
|
||||
/// fn from_ref(state: &AppState) -> Self {
|
||||
/// state.key.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let state = AppState {
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// key: Key::generate(),
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// .route("/set", post(set_secret))
|
||||
/// .route("/get", get(get_secret))
|
||||
/// // add extension with the key so `PrivateCookieJar` can access it
|
||||
/// .layer(Extension(key));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// .route("/get", get(get_secret));
|
||||
/// # let app: Router<_> = app;
|
||||
/// ```
|
||||
pub struct PrivateCookieJar<K = Key> {
|
||||
jar: cookie::CookieJar,
|
||||
key: Key,
|
||||
// The key used to extract the key extension. Allows users to use multiple keys for different
|
||||
// The key used to extract the key. Allows users to use multiple keys for different
|
||||
// jars. Maybe a library wants its own key.
|
||||
_marker: PhantomData<K>,
|
||||
}
|
||||
@@ -75,15 +87,17 @@ impl<K> fmt::Debug for PrivateCookieJar<K> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, K> FromRequest<B> for PrivateCookieJar<K>
|
||||
impl<S, B, K> FromRequest<S, B> for PrivateCookieJar<K>
|
||||
where
|
||||
B: Send,
|
||||
K: Into<Key> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
K: FromRef<S> + Into<Key>,
|
||||
{
|
||||
type Rejection = <axum::Extension<K> as FromRequest<B>>::Rejection;
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let key = req.extract::<Extension<K>>().await?.0.into();
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let k = K::from_ref(req.state());
|
||||
let key = k.into();
|
||||
let PrivateCookieJar {
|
||||
jar,
|
||||
key,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::{cookies_from_request, set_cookies};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
extract::{FromRef, FromRequest, RequestParts},
|
||||
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
|
||||
Extension,
|
||||
};
|
||||
use cookie::SignedJar;
|
||||
use cookie::{Cookie, Key};
|
||||
@@ -24,9 +23,8 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// Extension,
|
||||
/// routing::{post, get},
|
||||
/// extract::TypedHeader,
|
||||
/// extract::{TypedHeader, FromRef},
|
||||
/// response::{IntoResponse, Redirect},
|
||||
/// headers::authorization::{Authorization, Bearer},
|
||||
/// http::StatusCode,
|
||||
@@ -63,22 +61,36 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
||||
/// # todo!()
|
||||
/// }
|
||||
///
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// let key = Key::generate();
|
||||
/// // our application state
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {
|
||||
/// // that holds the key used to sign cookies
|
||||
/// key: Key,
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this impl tells `SignedCookieJar` how to access the key from our state
|
||||
/// impl FromRef<AppState> for Key {
|
||||
/// fn from_ref(state: &AppState) -> Self {
|
||||
/// state.key.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let state = AppState {
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// key: Key::generate(),
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// .route("/sessions", post(create_session))
|
||||
/// .route("/me", get(me))
|
||||
/// // add extension with the key so `SignedCookieJar` can access it
|
||||
/// .layer(Extension(key));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// .route("/me", get(me));
|
||||
/// # let app: Router<_> = app;
|
||||
/// ```
|
||||
pub struct SignedCookieJar<K = Key> {
|
||||
jar: cookie::CookieJar,
|
||||
key: Key,
|
||||
// The key used to extract the key extension. Allows users to use multiple keys for different
|
||||
// The key used to extract the key. Allows users to use multiple keys for different
|
||||
// jars. Maybe a library wants its own key.
|
||||
_marker: PhantomData<K>,
|
||||
}
|
||||
@@ -93,15 +105,17 @@ impl<K> fmt::Debug for SignedCookieJar<K> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, K> FromRequest<B> for SignedCookieJar<K>
|
||||
impl<S, B, K> FromRequest<S, B> for SignedCookieJar<K>
|
||||
where
|
||||
B: Send,
|
||||
K: Into<Key> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
K: FromRef<S> + Into<Key>,
|
||||
{
|
||||
type Rejection = <axum::Extension<K> as FromRequest<B>>::Rejection;
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let key = req.extract::<Extension<K>>().await?.0.into();
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let k = K::from_ref(req.state());
|
||||
let key = k.into();
|
||||
let SignedCookieJar {
|
||||
jar,
|
||||
key,
|
||||
|
||||
@@ -55,16 +55,17 @@ impl<T> Deref for Form<T> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Form<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Form<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = FormRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if req.method() == Method::GET {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_html_form::from_str(query)
|
||||
@@ -85,7 +86,7 @@ where
|
||||
}
|
||||
|
||||
// this is duplicated in `axum/src/extract/mod.rs`
|
||||
fn has_content_type<B>(req: &RequestParts<B>, expected_content_type: &mime::Mime) -> bool {
|
||||
fn has_content_type<S, B>(req: &RequestParts<S, B>, expected_content_type: &mime::Mime) -> bool {
|
||||
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
||||
content_type
|
||||
} else {
|
||||
|
||||
@@ -58,14 +58,15 @@ use std::ops::Deref;
|
||||
pub struct Query<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Query<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Query<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = QueryRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_html_form::from_str(query)
|
||||
.map_err(FailedToDeserializeQueryString::__private_new)?;
|
||||
|
||||
@@ -107,15 +107,16 @@ impl<E, R> DerefMut for WithRejection<E, R> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, E, R> FromRequest<B> for WithRejection<E, R>
|
||||
impl<B, E, R, S> FromRequest<S, B> for WithRejection<E, R>
|
||||
where
|
||||
B: Send,
|
||||
E: FromRequest<B>,
|
||||
S: Send,
|
||||
E: FromRequest<S, B>,
|
||||
R: From<E::Rejection> + IntoResponse,
|
||||
{
|
||||
type Rejection = R;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let extractor = req.extract::<E>().await?;
|
||||
Ok(WithRejection(extractor, PhantomData))
|
||||
}
|
||||
@@ -134,10 +135,14 @@ mod tests {
|
||||
struct TestRejection;
|
||||
|
||||
#[async_trait]
|
||||
impl<B: Send> FromRequest<B> for TestExtractor {
|
||||
impl<S, B> FromRequest<S, B> for TestExtractor
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ pub use self::or::Or;
|
||||
///
|
||||
/// The drawbacks of this trait is that you cannot apply middleware to individual handlers like you
|
||||
/// can with [`Handler::layer`].
|
||||
pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
pub trait HandlerCallWithExtractors<T, S, B>: Sized {
|
||||
/// The type of future calling this handler returns.
|
||||
type Future: Future<Output = Response> + Send + 'static;
|
||||
|
||||
/// Call the handler with the extracted inputs.
|
||||
fn call(self, extractors: T) -> <Self as HandlerCallWithExtractors<T, B>>::Future;
|
||||
fn call(self, state: S, extractors: T) -> <Self as HandlerCallWithExtractors<T, S, B>>::Future;
|
||||
|
||||
/// Conver this `HandlerCallWithExtractors` into [`Handler`].
|
||||
fn into_handler(self) -> IntoHandler<Self, T, B> {
|
||||
fn into_handler(self) -> IntoHandler<Self, T, S, B> {
|
||||
IntoHandler {
|
||||
handler: self,
|
||||
_marker: PhantomData,
|
||||
@@ -67,10 +67,14 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
/// struct AdminPermissions {}
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B: Send> FromRequest<B> for AdminPermissions {
|
||||
/// impl<S, B> FromRequest<S, B> for AdminPermissions
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// // check for admin permissions...
|
||||
/// # type Rejection = ();
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// }
|
||||
@@ -78,10 +82,14 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
/// struct User {}
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B: Send> FromRequest<B> for User {
|
||||
/// impl<S, B> FromRequest<S, B> for User
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// // check for a logged in user...
|
||||
/// # type Rejection = ();
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// }
|
||||
@@ -96,9 +104,9 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
/// );
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
fn or<R, Rt>(self, rhs: R) -> Or<Self, R, T, Rt, B>
|
||||
fn or<R, Rt>(self, rhs: R) -> Or<Self, R, T, Rt, S, B>
|
||||
where
|
||||
R: HandlerCallWithExtractors<Rt, B>,
|
||||
R: HandlerCallWithExtractors<Rt, S, B>,
|
||||
{
|
||||
Or {
|
||||
lhs: self,
|
||||
@@ -111,7 +119,7 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
macro_rules! impl_handler_call_with {
|
||||
( $($ty:ident),* $(,)? ) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<F, Fut, B, $($ty,)*> HandlerCallWithExtractors<($($ty,)*), B> for F
|
||||
impl<F, Fut, S, B, $($ty,)*> HandlerCallWithExtractors<($($ty,)*), S, B> for F
|
||||
where
|
||||
F: FnOnce($($ty,)*) -> Fut,
|
||||
Fut: Future + Send + 'static,
|
||||
@@ -122,8 +130,9 @@ macro_rules! impl_handler_call_with {
|
||||
|
||||
fn call(
|
||||
self,
|
||||
_state: S,
|
||||
($($ty,)*): ($($ty,)*),
|
||||
) -> <Self as HandlerCallWithExtractors<($($ty,)*), B>>::Future {
|
||||
) -> <Self as HandlerCallWithExtractors<($($ty,)*), S, B>>::Future {
|
||||
self($($ty,)*).map(IntoResponse::into_response)
|
||||
}
|
||||
}
|
||||
@@ -152,34 +161,35 @@ impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
|
||||
///
|
||||
/// Created with [`HandlerCallWithExtractors::into_handler`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct IntoHandler<H, T, B> {
|
||||
pub struct IntoHandler<H, T, S, B> {
|
||||
handler: H,
|
||||
_marker: PhantomData<fn() -> (T, B)>,
|
||||
_marker: PhantomData<fn() -> (T, S, B)>,
|
||||
}
|
||||
|
||||
impl<H, T, B> Handler<T, B> for IntoHandler<H, T, B>
|
||||
impl<H, T, S, B> Handler<T, S, B> for IntoHandler<H, T, S, B>
|
||||
where
|
||||
H: HandlerCallWithExtractors<T, B> + Clone + Send + 'static,
|
||||
T: FromRequest<B> + Send + 'static,
|
||||
H: HandlerCallWithExtractors<T, S, B> + Clone + Send + 'static,
|
||||
T: FromRequest<S, B> + Send + 'static,
|
||||
T::Rejection: Send,
|
||||
B: Send + 'static,
|
||||
S: Clone + Send + 'static,
|
||||
{
|
||||
type Future = BoxFuture<'static, Response>;
|
||||
|
||||
fn call(self, req: http::Request<B>) -> Self::Future {
|
||||
fn call(self, state: S, req: http::Request<B>) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let mut req = RequestParts::with_state(state.clone(), req);
|
||||
match req.extract::<T>().await {
|
||||
Ok(t) => self.handler.call(t).await,
|
||||
Ok(t) => self.handler.call(state, t).await,
|
||||
Err(rejection) => rejection.into_response(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, B> Copy for IntoHandler<H, T, B> where H: Copy {}
|
||||
impl<H, T, S, B> Copy for IntoHandler<H, T, S, B> where H: Copy {}
|
||||
|
||||
impl<H, T, B> Clone for IntoHandler<H, T, B>
|
||||
impl<H, T, S, B> Clone for IntoHandler<H, T, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
{
|
||||
|
||||
@@ -15,16 +15,16 @@ use std::{future::Future, marker::PhantomData};
|
||||
///
|
||||
/// Created with [`HandlerCallWithExtractors::or`](super::HandlerCallWithExtractors::or).
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Or<L, R, Lt, Rt, B> {
|
||||
pub struct Or<L, R, Lt, Rt, S, B> {
|
||||
pub(super) lhs: L,
|
||||
pub(super) rhs: R,
|
||||
pub(super) _marker: PhantomData<fn() -> (Lt, Rt, B)>,
|
||||
pub(super) _marker: PhantomData<fn() -> (Lt, Rt, S, B)>,
|
||||
}
|
||||
|
||||
impl<B, L, R, Lt, Rt> HandlerCallWithExtractors<Either<Lt, Rt>, B> for Or<L, R, Lt, Rt, B>
|
||||
impl<S, B, L, R, Lt, Rt> HandlerCallWithExtractors<Either<Lt, Rt>, S, B> for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: HandlerCallWithExtractors<Lt, B> + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, B> + Send + 'static,
|
||||
L: HandlerCallWithExtractors<Lt, S, B> + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, S, B> + Send + 'static,
|
||||
Rt: Send + 'static,
|
||||
Lt: Send + 'static,
|
||||
B: Send + 'static,
|
||||
@@ -37,46 +37,48 @@ where
|
||||
|
||||
fn call(
|
||||
self,
|
||||
state: S,
|
||||
extractors: Either<Lt, Rt>,
|
||||
) -> <Self as HandlerCallWithExtractors<Either<Lt, Rt>, B>>::Future {
|
||||
) -> <Self as HandlerCallWithExtractors<Either<Lt, Rt>, S, B>>::Future {
|
||||
match extractors {
|
||||
Either::E1(lt) => self
|
||||
.lhs
|
||||
.call(lt)
|
||||
.call(state, lt)
|
||||
.map(IntoResponse::into_response as _)
|
||||
.left_future(),
|
||||
Either::E2(rt) => self
|
||||
.rhs
|
||||
.call(rt)
|
||||
.call(state, rt)
|
||||
.map(IntoResponse::into_response as _)
|
||||
.right_future(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, L, R, Lt, Rt> Handler<(Lt, Rt), B> for Or<L, R, Lt, Rt, B>
|
||||
impl<S, B, L, R, Lt, Rt> Handler<(Lt, Rt), S, B> for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: HandlerCallWithExtractors<Lt, B> + Clone + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, B> + Clone + Send + 'static,
|
||||
Lt: FromRequest<B> + Send + 'static,
|
||||
Rt: FromRequest<B> + Send + 'static,
|
||||
L: HandlerCallWithExtractors<Lt, S, B> + Clone + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, S, B> + Clone + Send + 'static,
|
||||
Lt: FromRequest<S, B> + Send + 'static,
|
||||
Rt: FromRequest<S, B> + Send + 'static,
|
||||
Lt::Rejection: Send,
|
||||
Rt::Rejection: Send,
|
||||
B: Send + 'static,
|
||||
S: Clone + Send + 'static,
|
||||
{
|
||||
// this puts `futures_util` in our public API but thats fine in axum-extra
|
||||
type Future = BoxFuture<'static, Response>;
|
||||
|
||||
fn call(self, req: Request<B>) -> Self::Future {
|
||||
fn call(self, state: S, req: Request<B>) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let mut req = RequestParts::with_state(state.clone(), req);
|
||||
|
||||
if let Ok(lt) = req.extract::<Lt>().await {
|
||||
return self.lhs.call(lt).await;
|
||||
return self.lhs.call(state, lt).await;
|
||||
}
|
||||
|
||||
if let Ok(rt) = req.extract::<Rt>().await {
|
||||
return self.rhs.call(rt).await;
|
||||
return self.rhs.call(state, rt).await;
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
@@ -84,14 +86,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R, Lt, Rt, B> Copy for Or<L, R, Lt, Rt, B>
|
||||
impl<L, R, Lt, Rt, S, B> Copy for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: Copy,
|
||||
R: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
impl<L, R, Lt, Rt, B> Clone for Or<L, R, Lt, Rt, B>
|
||||
impl<L, R, Lt, Rt, S, B> Clone for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: Clone,
|
||||
R: Clone,
|
||||
|
||||
@@ -98,16 +98,17 @@ impl<S> JsonLines<S, AsResponse> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, T> FromRequest<B> for JsonLines<T, AsExtractor>
|
||||
impl<S, B, T> FromRequest<S, B> for JsonLines<T, AsExtractor>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Into<Bytes>,
|
||||
B::Error: Into<BoxError>,
|
||||
T: DeserializeOwned,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = BodyAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
// `Stream::lines` isn't a thing so we have to convert it into an `AsyncRead`
|
||||
// so we can call `AsyncRead::lines` and then convert it back to a `Stream`
|
||||
|
||||
|
||||
@@ -97,16 +97,17 @@ use std::ops::{Deref, DerefMut};
|
||||
pub struct ProtoBuf<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for ProtoBuf<T>
|
||||
impl<T, S, B> FromRequest<S, B> for ProtoBuf<T>
|
||||
where
|
||||
T: Message + Default,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ProtoBufRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let mut bytes = Bytes::from_request(req).await?;
|
||||
|
||||
match T::decode(&mut bytes) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
//! Additional types for defining routes.
|
||||
|
||||
use axum::{
|
||||
handler::Handler,
|
||||
handler::{Handler, HandlerWithoutStateExt},
|
||||
http::Request,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{any, MethodRouter},
|
||||
Router,
|
||||
};
|
||||
use std::{convert::Infallible, future::ready};
|
||||
use std::{convert::Infallible, future::ready, sync::Arc};
|
||||
use tower_service::Service;
|
||||
|
||||
mod resource;
|
||||
@@ -29,7 +30,7 @@ pub use self::typed::{FirstElementIs, TypedPath};
|
||||
pub use self::spa::SpaRouter;
|
||||
|
||||
/// Extension trait that adds additional methods to [`Router`].
|
||||
pub trait RouterExt<B>: sealed::Sealed {
|
||||
pub trait RouterExt<S, B>: sealed::Sealed {
|
||||
/// Add a typed `GET` route to the router.
|
||||
///
|
||||
/// The path will be inferred from the first argument to the handler function which must
|
||||
@@ -39,7 +40,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -52,7 +53,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -65,7 +66,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -78,7 +79,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -91,7 +92,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -104,7 +105,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -117,7 +118,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -130,7 +131,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
@@ -159,7 +160,14 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
/// .route_with_tsr("/bar/", get(|| async {}));
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
fn route_with_tsr<T>(self, path: &str, service: T) -> Self
|
||||
fn route_with_tsr(self, path: &str, method_router: MethodRouter<S, B>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Add another route to the router with an additional "trailing slash redirect" route.
|
||||
///
|
||||
/// This works like [`RouterExt::route_with_tsr`] but accepts any [`Service`].
|
||||
fn route_service_with_tsr<T>(self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
@@ -167,14 +175,15 @@ pub trait RouterExt<B>: sealed::Sealed {
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<B> RouterExt<B> for Router<B>
|
||||
impl<S, B> RouterExt<S, B> for Router<S, B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -184,7 +193,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -194,7 +203,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -204,7 +213,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -214,7 +223,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -224,7 +233,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -234,7 +243,7 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
@@ -244,41 +253,56 @@ where
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
self.route(P::PATH, axum::routing::trace(handler))
|
||||
}
|
||||
|
||||
fn route_with_tsr<T>(mut self, path: &str, service: T) -> Self
|
||||
fn route_with_tsr(mut self, path: &str, method_router: MethodRouter<S, B>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self = self.route(path, method_router);
|
||||
|
||||
let redirect_service = {
|
||||
let path: Arc<str> = path.into();
|
||||
(move || ready(Redirect::permanent(&path))).into_service()
|
||||
};
|
||||
|
||||
if let Some(path_without_trailing_slash) = path.strip_suffix('/') {
|
||||
self.route_service(path_without_trailing_slash, redirect_service)
|
||||
} else {
|
||||
self.route_service(&format!("{}/", path), redirect_service)
|
||||
}
|
||||
}
|
||||
|
||||
fn route_service_with_tsr<T>(mut self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
self = self.route(path, service);
|
||||
self = self.route_service(path, service);
|
||||
|
||||
let redirect = Redirect::permanent(path);
|
||||
|
||||
if let Some(path_without_trailing_slash) = path.strip_suffix('/') {
|
||||
self.route(
|
||||
path_without_trailing_slash,
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
any(move || ready(redirect.clone())),
|
||||
)
|
||||
} else {
|
||||
self.route(
|
||||
&format!("{}/", path),
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
)
|
||||
self.route(&format!("{}/", path), any(move || ready(redirect.clone())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<B> Sealed for axum::Router<B> {}
|
||||
impl<S, B> Sealed for axum::Router<S, B> {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
handler::Handler,
|
||||
http::Request,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, on, post, MethodFilter},
|
||||
routing::{delete, get, on, post, MethodFilter, MethodRouter},
|
||||
Router,
|
||||
};
|
||||
use std::{convert::Infallible, fmt};
|
||||
use tower_service::Service;
|
||||
|
||||
/// A resource which defines a set of conventional CRUD routes.
|
||||
///
|
||||
@@ -34,14 +30,15 @@ use tower_service::Service;
|
||||
/// .destroy(|Path(user_id): Path<u64>| async {});
|
||||
///
|
||||
/// let app = Router::new().merge(users);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub struct Resource<B = Body> {
|
||||
#[derive(Debug)]
|
||||
pub struct Resource<S = (), B = Body> {
|
||||
pub(crate) name: String,
|
||||
pub(crate) router: Router<B>,
|
||||
pub(crate) router: Router<S, B>,
|
||||
}
|
||||
|
||||
impl<B> Resource<B>
|
||||
impl<B> Resource<(), B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
{
|
||||
@@ -49,16 +46,29 @@ where
|
||||
///
|
||||
/// All routes will be nested at `/{resource_name}`.
|
||||
pub fn named(resource_name: &str) -> Self {
|
||||
Self::named_with((), resource_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Resource<S, B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Create a `Resource` with the given name and state.
|
||||
///
|
||||
/// All routes will be nested at `/{resource_name}`.
|
||||
pub fn named_with(state: S, resource_name: &str) -> Self {
|
||||
Self {
|
||||
name: resource_name.to_owned(),
|
||||
router: Default::default(),
|
||||
router: Router::with_state(state),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a handler at `GET /{resource_name}`.
|
||||
pub fn index<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.index_create_path();
|
||||
@@ -68,7 +78,7 @@ where
|
||||
/// Add a handler at `POST /{resource_name}`.
|
||||
pub fn create<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.index_create_path();
|
||||
@@ -78,7 +88,7 @@ where
|
||||
/// Add a handler at `GET /{resource_name}/new`.
|
||||
pub fn new<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = format!("/{}/new", self.name);
|
||||
@@ -88,7 +98,7 @@ where
|
||||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id`.
|
||||
pub fn show<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
@@ -98,7 +108,7 @@ where
|
||||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id/edit`.
|
||||
pub fn edit<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = format!("/{0}/:{0}_id/edit", self.name);
|
||||
@@ -108,7 +118,7 @@ where
|
||||
/// Add a handler at `PUT or PATCH /resource_name/:{resource_name}_id`.
|
||||
pub fn update<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
@@ -118,7 +128,7 @@ where
|
||||
/// Add a handler at `DELETE /{resource_name}/:{resource_name}_id`.
|
||||
pub fn destroy<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
@@ -133,13 +143,8 @@ where
|
||||
format!("/{0}/:{0}_id", self.name)
|
||||
}
|
||||
|
||||
fn route<T>(mut self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.router = self.router.route(path, svc);
|
||||
fn route(mut self, path: &str, method_router: MethodRouter<S, B>) -> Self {
|
||||
self.router = self.router.route(path, method_router);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -150,21 +155,13 @@ impl<B> From<Resource<B>> for Router<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Resource<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Resource")
|
||||
.field("name", &self.name)
|
||||
.field("router", &self.router)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
use axum::{extract::Path, http::Method, Router};
|
||||
use tower::ServiceExt;
|
||||
use http::Request;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn works() {
|
||||
@@ -220,7 +217,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
async fn call_route(app: &mut Router, method: Method, uri: &str) -> String {
|
||||
async fn call_route(app: &mut Router<()>, method: Method, uri: &str) -> String {
|
||||
let res = app
|
||||
.ready()
|
||||
.await
|
||||
|
||||
@@ -36,7 +36,7 @@ use tower_service::Service;
|
||||
/// .merge(spa)
|
||||
/// // we can still add other routes
|
||||
/// .route("/api/foo", get(api_foo));
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
///
|
||||
/// async fn api_foo() {}
|
||||
/// ```
|
||||
@@ -101,7 +101,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
||||
/// .index_file("another_file.html");
|
||||
///
|
||||
/// let app = Router::new().merge(spa);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub fn index_file<P>(mut self, path: P) -> Self
|
||||
where
|
||||
@@ -136,7 +136,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new().merge(spa);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub fn handle_error<T2, F2>(self, f: F2) -> SpaRouter<B, T2, F2> {
|
||||
SpaRouter {
|
||||
@@ -147,7 +147,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, T> From<SpaRouter<B, T, F>> for Router<B>
|
||||
impl<B, F, T> From<SpaRouter<B, T, F>> for Router<(), B>
|
||||
where
|
||||
F: Clone + Send + 'static,
|
||||
HandleError<Route<B, io::Error>, F, T>: Service<Request<B>, Error = Infallible>,
|
||||
@@ -162,7 +162,7 @@ where
|
||||
|
||||
Router::new()
|
||||
.nest(&spa.paths.assets_path, assets_service)
|
||||
.fallback(
|
||||
.fallback_service(
|
||||
get_service(ServeFile::new(&spa.paths.index_file)).handle_error(spa.handle_error),
|
||||
)
|
||||
}
|
||||
@@ -264,6 +264,13 @@ mod tests {
|
||||
|
||||
let spa = SpaRouter::new("/assets", "test_files").handle_error(handle_error);
|
||||
|
||||
Router::<Body>::new().merge(spa);
|
||||
Router::<_, Body>::new().merge(spa);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn works_with_router_with_state() {
|
||||
let _: Router<String> = Router::with_state(String::new())
|
||||
.merge(SpaRouter::new("/assets", "test_files"))
|
||||
.route("/", get(|_: axum::extract::State<String>| async {}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ use http::Uri;
|
||||
/// async fn users_destroy(_: UsersCollection) { /* ... */ }
|
||||
///
|
||||
/// #
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
///
|
||||
/// # Using `#[derive(TypedPath)]`
|
||||
|
||||
Reference in New Issue
Block a user