mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-28 00:00:20 +02:00
checkpoint
This commit is contained in:
+2
-2
@@ -2,8 +2,8 @@
|
||||
members = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"axum-extra",
|
||||
"axum-macros",
|
||||
# "axum-extra",
|
||||
# "axum-macros",
|
||||
|
||||
# internal crate used to bump the minimum versions we
|
||||
# get for some dependencies which otherwise wouldn't build
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::Handler;
|
||||
use crate::response::Response;
|
||||
use crate::{extract::State, response::Response};
|
||||
use http::Request;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
@@ -58,7 +58,16 @@ where
|
||||
use futures_util::future::FutureExt;
|
||||
|
||||
let handler = self.handler.clone();
|
||||
let state = req.extensions().get::<S>().unwrap().clone();
|
||||
let State(state) = req
|
||||
.extensions()
|
||||
.get::<State<S>>()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"no state of type `{}` was found. Please file an issue",
|
||||
std::any::type_name::<State<S>>()
|
||||
)
|
||||
})
|
||||
.clone();
|
||||
let future = Handler::call(handler, state, req);
|
||||
let future = future.map(Ok as _);
|
||||
|
||||
|
||||
+2
-2
@@ -382,8 +382,8 @@
|
||||
rust_2018_idioms,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
missing_debug_implementations,
|
||||
missing_docs
|
||||
// missing_debug_implementations,
|
||||
// missing_docs
|
||||
)]
|
||||
#![deny(unreachable_pub, private_in_public)]
|
||||
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
|
||||
|
||||
@@ -648,12 +648,30 @@ where
|
||||
where
|
||||
H: Handler<S, T, B>,
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
self.fallback_boxed_response_body(IntoExtensionService::new(handler))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, R> MethodRouter<S, B, Infallible, R> {
|
||||
pub(crate) fn change_state_marker<R2>(self) -> MethodRouter<S, B, Infallible, R2> {
|
||||
MethodRouter {
|
||||
state: self.state,
|
||||
get: self.get,
|
||||
head: self.head,
|
||||
delete: self.delete,
|
||||
options: self.options,
|
||||
patch: self.patch,
|
||||
post: self.post,
|
||||
put: self.put,
|
||||
trace: self.trace,
|
||||
fallback: self.fallback,
|
||||
allow_header: self.allow_header,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> MethodRouter<S, B, Infallible, WithState>
|
||||
where
|
||||
B: Send + 'static,
|
||||
|
||||
+77
-52
@@ -3,7 +3,7 @@
|
||||
use self::{future::RouteFuture, not_found::NotFound};
|
||||
use crate::{
|
||||
body::{boxed, Body, Bytes, HttpBody},
|
||||
extract::connect_info::IntoMakeServiceWithConnectInfo,
|
||||
extract::{connect_info::IntoMakeServiceWithConnectInfo, State},
|
||||
handler::{Handler, IntoExtensionService},
|
||||
response::Response,
|
||||
routing::strip_prefix::StripPrefix,
|
||||
@@ -168,6 +168,14 @@ where
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_state<F, OuterState>(self, f: F) -> Router<OuterState, B, MissingState>
|
||||
where
|
||||
// TODO(david): which Fn?
|
||||
F: FnOnce(OuterState) -> S,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Router<S, B, WithState>
|
||||
@@ -194,7 +202,7 @@ where
|
||||
impl<S, B, R> Router<S, B, R>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: 'static,
|
||||
S: Clone + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
#[doc = include_str!("../docs/routing/route.md")]
|
||||
@@ -205,7 +213,41 @@ where
|
||||
// routers containing handlers
|
||||
method_router: MethodRouter<S, B, Infallible, MissingState>,
|
||||
) -> Self {
|
||||
self
|
||||
validate_path_for_route(path);
|
||||
|
||||
let id = RouteId::next();
|
||||
|
||||
match self
|
||||
.node
|
||||
.path_to_route_id
|
||||
.get(path)
|
||||
.and_then(|route_id| self.routes.get(route_id).map(|svc| (*route_id, svc)))
|
||||
{
|
||||
Some((route_id, Endpoint::MethodRouter(prev_method_router))) => {
|
||||
// if we're adding a new `MethodRouter` to a route that already has one just
|
||||
// merge them. This makes `.route("/", get(_)).route("/", post(_))` work
|
||||
let service =
|
||||
Endpoint::MethodRouter(prev_method_router.clone().merge(method_router));
|
||||
|
||||
self.routes.insert(route_id, service);
|
||||
|
||||
self
|
||||
}
|
||||
Some((_, Endpoint::Route(_))) => {
|
||||
// if the endpoint isn't a `MethodRouter` then we have no way of merging things so
|
||||
// just panic
|
||||
panic!("A route for `{}` with a different HTTP method already exists and the routes could not be merge", path)
|
||||
}
|
||||
None => {
|
||||
// the state will be provided later in `<Router as Service>::call`, so its safe to
|
||||
// ignore that it hasn't been provided yet
|
||||
let service = Endpoint::MethodRouter(method_router.change_state_marker());
|
||||
|
||||
self.insert_endpoint(path, id, service);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO(david): docs
|
||||
@@ -214,57 +256,32 @@ where
|
||||
T: Service<Request<B>, Response = Response, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
let service = match try_downcast::<Router<S, B, WithState>, _>(service) {
|
||||
Ok(_) => {
|
||||
panic!("Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead")
|
||||
}
|
||||
Err(svc) => svc,
|
||||
};
|
||||
|
||||
validate_path_for_route(path);
|
||||
|
||||
let id = RouteId::next();
|
||||
let service = Endpoint::Route(Route::new(service));
|
||||
|
||||
self.insert_endpoint(path, id, service);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// if path.is_empty() {
|
||||
// panic!("Paths must start with a `/`. Use \"/\" for root routes");
|
||||
// } else if !path.starts_with('/') {
|
||||
// panic!("Paths must start with a `/`");
|
||||
// }
|
||||
fn insert_endpoint(&mut self, path: &str, id: RouteId, endpoint: Endpoint<S, B, R>) {
|
||||
let mut node =
|
||||
Arc::try_unwrap(Arc::clone(&self.node)).unwrap_or_else(|node| (*node).clone());
|
||||
if let Err(err) = node.insert(path, id) {
|
||||
panic!("Invalid route: {}", err);
|
||||
}
|
||||
self.node = Arc::new(node);
|
||||
|
||||
// // Downcase to `WithState` rather than `R` because `Router<S, B, R>` only implements
|
||||
// // `Service` if `R == WithState` so any other type of `R` cannot be passed to `.router` in
|
||||
// // the first place
|
||||
// let service = match try_downcast::<Router<S, B, WithState>, _>(service) {
|
||||
// Ok(_) => {
|
||||
// panic!("Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead")
|
||||
// }
|
||||
// Err(svc) => svc,
|
||||
// };
|
||||
|
||||
// let id = RouteId::next();
|
||||
|
||||
// let service = match try_downcast::<MethodRouter<B, Infallible>, _>(service) {
|
||||
// Ok(method_router) => {
|
||||
// if let Some((route_id, Endpoint::MethodRouter(prev_method_router))) = self
|
||||
// .node
|
||||
// .path_to_route_id
|
||||
// .get(path)
|
||||
// .and_then(|route_id| self.routes.get(route_id).map(|svc| (*route_id, svc)))
|
||||
// {
|
||||
// // if we're adding a new `MethodRouter` to a route that already has one just
|
||||
// // merge them. This makes `.route("/", get(_)).route("/", post(_))` work
|
||||
// let service =
|
||||
// Endpoint::MethodRouter(prev_method_router.clone().merge(method_router));
|
||||
// self.routes.insert(route_id, service);
|
||||
// return self;
|
||||
// } else {
|
||||
// Endpoint::MethodRouter(method_router)
|
||||
// }
|
||||
// }
|
||||
// Err(service) => Endpoint::Route(Route::new(service)),
|
||||
// };
|
||||
|
||||
// let mut node =
|
||||
// Arc::try_unwrap(Arc::clone(&self.node)).unwrap_or_else(|node| (*node).clone());
|
||||
// if let Err(err) = node.insert(path, id) {
|
||||
// panic!("Invalid route: {}", err);
|
||||
// }
|
||||
// self.node = Arc::new(node);
|
||||
|
||||
// self.routes.insert(id, service);
|
||||
|
||||
// self
|
||||
self.routes.insert(id, endpoint);
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/routing/nest.md")]
|
||||
@@ -599,7 +616,7 @@ where
|
||||
// the `unwrap` is safe because `self.state` is always some if `R = WithState`, which it is
|
||||
let prev = req
|
||||
.extensions_mut()
|
||||
.insert(crate::extract::State(self.state.as_ref().unwrap().clone()));
|
||||
.insert(State(self.state.as_ref().unwrap().clone()));
|
||||
debug_assert!(prev.is_none());
|
||||
|
||||
match self.node.at(&path) {
|
||||
@@ -622,6 +639,14 @@ pub enum MissingState {}
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WithState {}
|
||||
|
||||
fn validate_path_for_route(path: &str) {
|
||||
if path.is_empty() {
|
||||
panic!("Paths must start with a `/`. Use \"/\" for root routes");
|
||||
} else if !path.starts_with('/') {
|
||||
panic!("Paths must start with a `/`");
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_path_for_nest(path: &mut &str) {
|
||||
if path.is_empty() {
|
||||
// nesting at `""` and `"/"` should mean the same thing
|
||||
|
||||
@@ -414,3 +414,8 @@ async fn middleware_that_return_early() {
|
||||
);
|
||||
assert_eq!(client.get("/public").send().await.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn merging_with_different_state() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
body::{Bytes, Empty},
|
||||
error_handling::HandleErrorLayer,
|
||||
extract::{self, Path},
|
||||
extract::{self, Path, State},
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, get_service, on, on_service, patch, patch_service, post, MethodFilter},
|
||||
@@ -34,31 +34,19 @@ async fn hello_world() {
|
||||
"Hello, World!"
|
||||
}
|
||||
|
||||
async fn foo(_: Request<Body>) -> &'static str {
|
||||
"foo"
|
||||
}
|
||||
let app = Router::without_state()
|
||||
.route("/", get(root))
|
||||
.route_service("/foo", get(root).state(()));
|
||||
|
||||
async fn users_create(_: Request<Body>) -> &'static str {
|
||||
"users#create"
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(root).post(foo))
|
||||
.route("/users", post(users_create));
|
||||
|
||||
let client = TestClient::new(app.state(()));
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "Hello, World!");
|
||||
|
||||
let res = client.post("/").send().await;
|
||||
let res = client.get("/foo").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "foo");
|
||||
|
||||
let res = client.post("/users").send().await;
|
||||
let body = res.text().await;
|
||||
assert_eq!(body, "users#create");
|
||||
assert_eq!(body, "Hello, World!");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -455,6 +443,17 @@ async fn routing_to_router_panics() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic(
|
||||
expected = "A route for `/` with a different HTTP method already exists and the routes could not be merge"
|
||||
)]
|
||||
async fn conflicting_method_router_and_opaque() {
|
||||
let app = Router::without_state()
|
||||
.route_service("/", get(|| async {}).state(()))
|
||||
.route("/", post(|| async {}));
|
||||
TestClient::new(app);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn route_layer() {
|
||||
let app = Router::new()
|
||||
@@ -502,7 +501,7 @@ async fn different_methods_added_in_different_routes() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn different_methods_added_in_different_routes_deeply_nested() {
|
||||
let app = Router::new()
|
||||
let app = Router::with_state(())
|
||||
.route("/foo/bar/baz", get(|| async { "GET" }))
|
||||
.nest(
|
||||
"/foo",
|
||||
@@ -512,7 +511,7 @@ async fn different_methods_added_in_different_routes_deeply_nested() {
|
||||
),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app.state(()));
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo/bar/baz").send().await;
|
||||
let body = res.text().await;
|
||||
@@ -677,3 +676,22 @@ async fn limited_body_with_streaming_body() {
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn extracting_state() {
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
value: &'static str,
|
||||
}
|
||||
|
||||
async fn handler(State(app_state): State<AppState>) -> &'static str {
|
||||
app_state.value
|
||||
}
|
||||
|
||||
let app = Router::with_state(AppState { value: "foo" }).route("/", get(handler));
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
assert_eq!(res.text().await, "foo");
|
||||
}
|
||||
|
||||
@@ -386,6 +386,33 @@ async fn nest_with_and_without_trailing() {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nesting_with_different_state() {
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
inner: InnerState,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InnerState {}
|
||||
|
||||
impl From<State> for InnerState {
|
||||
fn from(state: State) -> Self {
|
||||
state.inner
|
||||
}
|
||||
}
|
||||
|
||||
let inner_router = Router::<InnerState, Body, _>::new();
|
||||
|
||||
let router_router = Router::<State, Body, _>::new()
|
||||
.state(State {
|
||||
inner: InnerState {},
|
||||
})
|
||||
.nest("/", inner_router.map_state(Into::into));
|
||||
|
||||
todo!();
|
||||
}
|
||||
|
||||
macro_rules! nested_route_test {
|
||||
(
|
||||
$name:ident,
|
||||
|
||||
Reference in New Issue
Block a user