add state parameter to router

This commit is contained in:
David Pedersen
2022-07-03 00:44:19 +02:00
parent 7ed35d2b5f
commit 43169f96cd
30 changed files with 468 additions and 257 deletions
+16
View File
@@ -272,3 +272,19 @@ where
Ok(T::from_request(req).await)
}
}
/// TODO(david): docs
#[derive(Clone, Copy, Debug, Default)]
pub struct State<S>(pub S);
#[async_trait]
impl<S, B> FromRequest<B> for State<S>
where
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
todo!()
}
}
+6 -4
View File
@@ -226,12 +226,13 @@ mod tests {
jar.remove(Cookie::named("key"))
}
let app = Router::<Body>::new()
let app = Router::<_, Body, _>::new()
.route("/set", get(set_cookie))
.route("/get", get(get_cookie))
.route("/remove", get(remove_cookie))
.layer(Extension(Key::generate()))
.layer(Extension(CustomKey(Key::generate())));
.layer(Extension(CustomKey(Key::generate())))
.state(());
let res = app
.clone()
@@ -294,9 +295,10 @@ mod tests {
format!("{:?}", jar.get("key"))
}
let app = Router::<Body>::new()
let app = Router::<_, Body, _>::new()
.route("/get", get(get_cookie))
.layer(Extension(Key::generate()));
.layer(Extension(Key::generate()))
.state(());
let res = app
.clone()
+6 -4
View File
@@ -116,10 +116,12 @@ mod tests {
values: Vec<String>,
}
let app = Router::new().route(
"/",
post(|Form(data): Form<Data>| async move { data.values.join(",") }),
);
let app = Router::new()
.route(
"/",
post(|Form(data): Form<Data>| async move { data.values.join(",") }),
)
.state(());
let client = TestClient::new(app);
+6 -4
View File
@@ -97,10 +97,12 @@ mod tests {
values: Vec<String>,
}
let app = Router::new().route(
"/",
post(|Query(data): Query<Data>| async move { data.values.join(",") }),
);
let app = Router::new()
.route(
"/",
post(|Query(data): Query<Data>| async move { data.values.join(",") }),
)
.state(());
let client = TestClient::new(app);
+30 -26
View File
@@ -217,22 +217,24 @@ mod tests {
#[tokio::test]
async fn extractor() {
let app = Router::new().route(
"/",
post(|mut stream: JsonLines<User>| async move {
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 1 });
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 2 });
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 3 });
let app = Router::new()
.route(
"/",
post(|mut stream: JsonLines<User>| async move {
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 1 });
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 2 });
assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 3 });
// sources are downcastable to `serde_json::Error`
let err = stream.next().await.unwrap().unwrap_err();
let _: &serde_json::Error = err
.source()
.unwrap()
.downcast_ref::<serde_json::Error>()
.unwrap();
}),
);
// sources are downcastable to `serde_json::Error`
let err = stream.next().await.unwrap().unwrap_err();
let _: &serde_json::Error = err
.source()
.unwrap()
.downcast_ref::<serde_json::Error>()
.unwrap();
}),
)
.state(());
let client = TestClient::new(app);
@@ -255,17 +257,19 @@ mod tests {
#[tokio::test]
async fn response() {
let app = Router::new().route(
"/",
get(|| async {
let values = futures_util::stream::iter(vec![
Ok::<_, Infallible>(User { id: 1 }),
Ok::<_, Infallible>(User { id: 2 }),
Ok::<_, Infallible>(User { id: 3 }),
]);
JsonLines::new(values)
}),
);
let app = Router::new()
.route(
"/",
get(|| async {
let values = futures_util::stream::iter(vec![
Ok::<_, Infallible>(User { id: 1 }),
Ok::<_, Infallible>(User { id: 2 }),
Ok::<_, Infallible>(User { id: 3 }),
]);
JsonLines::new(values)
}),
)
.state(());
let client = TestClient::new(app);
+7 -4
View File
@@ -29,7 +29,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, R>: 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
@@ -166,9 +166,11 @@ pub trait RouterExt<B>: sealed::Sealed {
Self: Sized;
}
impl<B> RouterExt<B> for Router<B>
impl<S, B, R> RouterExt<S, B, R> for Router<S, B, R>
where
B: axum::body::HttpBody + Send + 'static,
R: 'static,
S: 'static,
{
#[cfg(feature = "typed-routing")]
fn typed_get<H, T, P>(self, handler: H) -> Self
@@ -276,7 +278,7 @@ where
mod sealed {
pub trait Sealed {}
impl<B> Sealed for axum::Router<B> {}
impl<S, B, R> Sealed for axum::Router<S, B, R> {}
}
#[cfg(test)]
@@ -289,7 +291,8 @@ mod tests {
async fn test_tsr() {
let app = Router::new()
.route_with_tsr("/foo", get(|| async {}))
.route_with_tsr("/bar/", get(|| async {}));
.route_with_tsr("/bar/", get(|| async {}))
.state(());
let client = TestClient::new(app);
+45 -12
View File
@@ -3,10 +3,10 @@ use axum::{
handler::Handler,
http::Request,
response::Response,
routing::{delete, get, on, post, MethodFilter},
routing::{delete, get, on, post, MethodFilter, MissingState, WithState},
Router,
};
use std::convert::Infallible;
use std::{convert::Infallible, fmt};
use tower_service::Service;
/// A resource which defines a set of conventional CRUD routes.
@@ -47,13 +47,24 @@ use tower_service::Service;
/// let app = Router::new().merge(users);
/// # let _: Router<axum::body::Body> = app;
/// ```
#[derive(Debug)]
pub struct Resource<B = Body> {
pub struct Resource<S, B = Body, R = MissingState> {
pub(crate) name: String,
pub(crate) router: Router<B>,
pub(crate) router: Router<S, B, R>,
}
impl<B> Resource<B>
impl<S, B, R> fmt::Debug for Resource<S, B, R>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Resource")
.field("name", &self.name)
.field("router", &self.router)
.finish()
}
}
impl<S, B> Resource<S, B, MissingState>
where
B: axum::body::HttpBody + Send + 'static,
{
@@ -67,6 +78,24 @@ where
}
}
/// TODO(david): docs
pub fn state(self, state: S) -> Resource<S, B, WithState>
where
S: Clone,
{
Resource {
name: self.name,
router: self.router.state(state),
}
}
}
impl<S, B, R> Resource<S, B, R>
where
B: axum::body::HttpBody + Send + 'static,
S: 'static,
R: 'static,
{
/// Add a handler at `GET /{resource_name}`.
pub fn index<H, T>(self, handler: H) -> Self
where
@@ -140,7 +169,7 @@ where
/// Nest another router at the "member level".
///
/// The routes will be nested at `/{resource_name}/:{resource_name}_id`.
pub fn nest(mut self, router: Router<B>) -> Self {
pub fn nest(mut self, router: Router<S, B, MissingState>) -> Self {
let path = self.show_update_destroy_path();
self.router = self.router.nest(&path, router);
self
@@ -149,7 +178,7 @@ where
/// Nest another router at the "collection level".
///
/// The routes will be nested at `/{resource_name}`.
pub fn nest_collection(mut self, router: Router<B>) -> Self {
pub fn nest_collection(mut self, router: Router<S, B, MissingState>) -> Self {
let path = self.index_create_path();
self.router = self.router.nest(&path, router);
self
@@ -173,8 +202,8 @@ where
}
}
impl<B> From<Resource<B>> for Router<B> {
fn from(resource: Resource<B>) -> Self {
impl<S, B> From<Resource<S, B, MissingState>> for Router<S, B, MissingState> {
fn from(resource: Resource<S, B, MissingState>) -> Self {
resource.router
}
}
@@ -204,7 +233,7 @@ mod tests {
Router::new().route("/featured", get(|| async move { "users#featured" })),
);
let mut app = Router::new().merge(users);
let mut app = Router::new().merge(users).state(());
assert_eq!(
call_route(&mut app, Method::GET, "/users").await,
@@ -257,7 +286,11 @@ mod tests {
);
}
async fn call_route(app: &mut Router, method: Method, uri: &str) -> String {
async fn call_route(
app: &mut Router<(), Body, axum::routing::WithState>,
method: Method,
uri: &str,
) -> String {
let res = app
.ready()
.await
+9 -6
View File
@@ -2,7 +2,7 @@ use axum::{
body::{Body, HttpBody},
error_handling::HandleError,
response::Response,
routing::{get_service, Route},
routing::{get_service, MissingState, Route},
Router,
};
use http::{Request, StatusCode};
@@ -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, S> From<SpaRouter<B, T, F>> for Router<S, B, MissingState>
where
F: Clone + Send + 'static,
HandleError<Route<B, io::Error>, F, T>:
@@ -155,6 +155,7 @@ where
<HandleError<Route<B, io::Error>, F, T> as Service<Request<B>>>::Future: Send,
B: HttpBody + Send + 'static,
T: 'static,
S: 'static,
{
fn from(spa: SpaRouter<B, T, F>) -> Self {
let assets_service = get_service(ServeDir::new(&spa.paths.assets_dir))
@@ -214,7 +215,8 @@ mod tests {
async fn basic() {
let app = Router::new()
.route("/foo", get(|| async { "GET /foo" }))
.merge(SpaRouter::new("/assets", "test_files"));
.merge(SpaRouter::new("/assets", "test_files"))
.state(());
let client = TestClient::new(app);
let res = client.get("/").send().await;
@@ -239,8 +241,9 @@ mod tests {
#[tokio::test]
async fn setting_index_file() {
let app =
Router::new().merge(SpaRouter::new("/assets", "test_files").index_file("index_2.html"));
let app = Router::new()
.merge(SpaRouter::new("/assets", "test_files").index_file("index_2.html"))
.state(());
let client = TestClient::new(app);
let res = client.get("/").send().await;
@@ -264,6 +267,6 @@ mod tests {
let spa = SpaRouter::new("/assets", "test_files").handle_error(handle_error);
Router::<Body>::new().merge(spa);
Router::<(), Body, _>::new().merge(spa);
}
}
+2 -2
View File
@@ -158,7 +158,7 @@ mod tests {
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
let app = Router::new().route("/", get(handler));
let app = Router::new().route("/", get(handler)).state(());
let server = Server::from_tcp(listener)
.unwrap()
.serve(app.into_make_service_with_connect_info::<SocketAddr>());
@@ -198,7 +198,7 @@ mod tests {
let (tx, rx) = tokio::sync::oneshot::channel();
tokio::spawn(async move {
let app = Router::new().route("/", get(handler));
let app = Router::new().route("/", get(handler)).state(());
let server = Server::from_tcp(listener)
.unwrap()
.serve(app.into_make_service_with_connect_info::<MyConnectInfo>());
+15 -7
View File
@@ -115,10 +115,12 @@ mod tests {
const LIMIT: u64 = 8;
let app = Router::new().route(
"/",
post(|_body: ContentLengthLimit<Bytes, LIMIT>| async {}),
);
let app = Router::new()
.route(
"/",
post(|_body: ContentLengthLimit<Bytes, LIMIT>| async {}),
)
.state(());
let client = TestClient::new(app);
let res = client
@@ -154,7 +156,9 @@ mod tests {
#[tokio::test]
async fn get_request_without_content_length_is_accepted() {
let app = Router::new().route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}));
let app = Router::new()
.route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}))
.state(());
let client = TestClient::new(app);
@@ -164,7 +168,9 @@ mod tests {
#[tokio::test]
async fn get_request_with_content_length_is_rejected() {
let app = Router::new().route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}));
let app = Router::new()
.route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}))
.state(());
let client = TestClient::new(app);
@@ -179,7 +185,9 @@ mod tests {
#[tokio::test]
async fn get_request_with_chunked_encoding_is_rejected() {
let app = Router::new().route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}));
let app = Router::new()
.route("/", get(|_body: ContentLengthLimit<Bytes, 1337>| async {}))
.state(());
let client = TestClient::new(app);
+1 -1
View File
@@ -84,7 +84,7 @@ mod tests {
host
}
TestClient::new(Router::new().route("/", get(host_as_body)))
TestClient::new(Router::new().route("/", get(host_as_body)).state(()))
}
#[tokio::test]
+13 -8
View File
@@ -150,7 +150,8 @@ mod tests {
Router::new().route("/assets/*path", get(handler)),
)
.nest_service("/foo", handler.into_service())
.layer(tower::layer::layer_fn(SetMatchedPathExtension));
.layer(tower::layer::layer_fn(SetMatchedPathExtension))
.state(());
let client = TestClient::new(app);
@@ -181,13 +182,17 @@ mod tests {
#[tokio::test]
async fn nested_opaque_routers_append_to_matched_path() {
let app = Router::new().nest_service(
"/:a",
Router::new().route(
"/:b",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
),
);
let app = Router::new()
.nest_service(
"/:a",
Router::new()
.route(
"/:b",
get(|path: MatchedPath| async move { path.as_str().to_owned() }),
)
.state(()),
)
.state(());
let client = TestClient::new(app);
+4 -2
View File
@@ -16,7 +16,7 @@ mod raw_query;
mod request_parts;
#[doc(inline)]
pub use axum_core::extract::{FromRequest, RequestParts};
pub use axum_core::extract::{FromRequest, RequestParts, State};
#[doc(inline)]
#[allow(deprecated)]
@@ -103,7 +103,9 @@ mod tests {
#[tokio::test]
async fn consume_body() {
let app = Router::new().route("/", get(|body: String| async { body }));
let app = Router::new()
.route("/", get(|body: String| async { body }))
.state(());
let client = TestClient::new(app);
let res = client.get("/").body("foo").send().await;
+1 -1
View File
@@ -257,7 +257,7 @@ mod tests {
assert!(multipart.next_field().await.unwrap().is_none());
}
let app = Router::new().route("/", post(handle));
let app = Router::new().route("/", post(handle)).state(());
let client = TestClient::new(app);
+48 -32
View File
@@ -418,15 +418,17 @@ mod tests {
#[tokio::test]
async fn extracting_url_params() {
let app = Router::new().route(
"/users/:id",
get(|Path(id): Path<i32>| async move {
assert_eq!(id, 42);
})
.post(|Path(params_map): Path<HashMap<String, i32>>| async move {
assert_eq!(params_map.get("id").unwrap(), &1337);
}),
);
let app = Router::new()
.route(
"/users/:id",
get(|Path(id): Path<i32>| async move {
assert_eq!(id, 42);
})
.post(|Path(params_map): Path<HashMap<String, i32>>| async move {
assert_eq!(params_map.get("id").unwrap(), &1337);
}),
)
.state(());
let client = TestClient::new(app);
@@ -439,7 +441,9 @@ mod tests {
#[tokio::test]
async fn extracting_url_params_multiple_times() {
let app = Router::new().route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}));
let app = Router::new()
.route("/users/:id", get(|_: Path<i32>, _: Path<String>| async {}))
.state(());
let client = TestClient::new(app);
@@ -449,10 +453,12 @@ mod tests {
#[tokio::test]
async fn percent_decoding() {
let app = Router::new().route(
"/:key",
get(|Path(param): Path<String>| async move { param }),
);
let app = Router::new()
.route(
"/:key",
get(|Path(param): Path<String>| async move { param }),
)
.state(());
let client = TestClient::new(app);
@@ -471,7 +477,8 @@ mod tests {
.route(
"/u/:key",
get(|Path(param): Path<u128>| async move { param.to_string() }),
);
)
.state(());
let client = TestClient::new(app);
@@ -494,7 +501,8 @@ mod tests {
get(|Path(params): Path<HashMap<String, String>>| async move {
params.get("rest").unwrap().clone()
}),
);
)
.state(());
let client = TestClient::new(app);
@@ -507,7 +515,7 @@ mod tests {
#[tokio::test]
async fn captures_dont_match_empty_segments() {
let app = Router::new().route("/:key", get(|| async {}));
let app = Router::new().route("/:key", get(|| async {})).state(());
let client = TestClient::new(app);
@@ -520,7 +528,9 @@ mod tests {
#[tokio::test]
async fn when_extensions_are_missing() {
let app = Router::new().route("/:key", get(|_: Request<Body>, _: Path<String>| async {}));
let app = Router::new()
.route("/:key", get(|_: Request<Body>, _: Path<String>| async {}))
.state(());
let client = TestClient::new(app);
@@ -545,7 +555,9 @@ mod tests {
}
}
let app = Router::new().route("/:key", get(|param: Path<Param>| async move { param.0 .0 }));
let app = Router::new()
.route("/:key", get(|param: Path<Param>| async move { param.0 .0 }))
.state(());
let client = TestClient::new(app);
@@ -559,7 +571,9 @@ mod tests {
#[tokio::test]
async fn two_path_extractors() {
let app = Router::new().route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}));
let app = Router::new()
.route("/:a/:b", get(|_: Path<String>, _: Path<String>| async {}))
.state(());
let client = TestClient::new(app);
@@ -574,18 +588,20 @@ mod tests {
#[tokio::test]
async fn deserialize_into_vec_of_tuples() {
let app = Router::new().route(
"/:a/:b",
get(|Path(params): Path<Vec<(String, String)>>| async move {
assert_eq!(
params,
vec![
("a".to_owned(), "foo".to_owned()),
("b".to_owned(), "bar".to_owned())
]
);
}),
);
let app = Router::new()
.route(
"/:a/:b",
get(|Path(params): Path<Vec<(String, String)>>| async move {
assert_eq!(
params,
vec![
("a".to_owned(), "foo".to_owned()),
("b".to_owned(), "bar".to_owned())
]
);
}),
)
.state(());
let client = TestClient::new(app);
+8 -3
View File
@@ -223,7 +223,7 @@ mod tests {
async fn multiple_request_extractors() {
async fn handler(_: Request<Body>, _: Request<Body>) {}
let app = Router::new().route("/", post(handler));
let app = Router::new().route("/", post(handler)).state(());
let client = TestClient::new(app);
@@ -248,7 +248,12 @@ mod tests {
parts.extensions.get::<Ext>().unwrap();
}
let client = TestClient::new(Router::new().route("/", get(handler)).layer(Extension(Ext)));
let client = TestClient::new(
Router::new()
.route("/", get(handler))
.layer(Extension(Ext))
.state(()),
);
let res = client.get("/").header("x-foo", "123").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -263,7 +268,7 @@ mod tests {
assert_eq!(body, "foo");
}
let client = TestClient::new(Router::new().route("/", get(handler)));
let client = TestClient::new(Router::new().route("/", get(handler)).state(()));
let res = client.get("/").body("foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
+12 -4
View File
@@ -221,7 +221,9 @@ mod tests {
foo: String,
}
let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
let app = Router::new()
.route("/", post(|input: Json<Input>| async { input.0.foo }))
.state(());
let client = TestClient::new(app);
let res = client.post("/").json(&json!({ "foo": "bar" })).send().await;
@@ -237,7 +239,9 @@ mod tests {
foo: String,
}
let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
let app = Router::new()
.route("/", post(|input: Json<Input>| async { input.0.foo }))
.state(());
let client = TestClient::new(app);
let res = client.post("/").body(r#"{ "foo": "bar" }"#).send().await;
@@ -253,7 +257,9 @@ mod tests {
async fn valid_json_content_type(content_type: &str) -> bool {
println!("testing {:?}", content_type);
let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
let app = Router::new()
.route("/", post(|Json(_): Json<Value>| async {}))
.state(());
let res = TestClient::new(app)
.post("/")
@@ -274,7 +280,9 @@ mod tests {
#[tokio::test]
async fn invalid_json_syntax() {
let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {}));
let app = Router::new()
.route("/", post(|_: Json<serde_json::Value>| async {}))
.state(());
let client = TestClient::new(app);
let res = client
+2 -1
View File
@@ -302,7 +302,8 @@ mod tests {
async fn handler() {}
let app = Router::new().route("/", get(handler.layer(from_extractor::<RequireAuth>())));
let app =
Router::with_state(()).route("/", get(handler.layer(from_extractor::<RequireAuth>())));
let client = TestClient::new(app);
+1 -1
View File
@@ -296,7 +296,7 @@ mod tests {
(&headers["x-axum-test"]).to_str().unwrap().to_owned()
}
let app = Router::new()
let app = Router::with_state(())
.route("/", get(handle))
.layer(from_fn(insert_header));
+2 -2
View File
@@ -93,7 +93,7 @@ mod tests {
}
}
Router::<Body>::new()
Router::<(), Body, _>::new()
.route("/", get(impl_trait_ok))
.route("/", get(impl_trait_err))
.route("/", get(impl_trait_both))
@@ -203,7 +203,7 @@ mod tests {
)
}
Router::<Body>::new()
Router::<(), Body, _>::new()
.route("/", get(status))
.route("/", get(status_headermap))
.route("/", get(status_header_array))
+3 -3
View File
@@ -508,7 +508,7 @@ mod tests {
#[tokio::test]
async fn basic() {
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
get(|| async {
let stream = stream::iter(vec![
@@ -553,7 +553,7 @@ mod tests {
async fn keep_alive() {
const DELAY: Duration = Duration::from_secs(5);
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
get(|| async {
let stream = stream::repeat_with(|| Event::default().data("msg"))
@@ -589,7 +589,7 @@ mod tests {
async fn keep_alive_ends_when_the_stream_ends() {
const DELAY: Duration = Duration::from_secs(5);
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
get(|| async {
let stream = stream::repeat_with(|| Event::default().data("msg"))
+22 -20
View File
@@ -1193,26 +1193,28 @@ mod tests {
#[allow(dead_code)]
fn buiding_complex_router() {
let app = crate::Router::new().route(
"/",
// use the all the things :bomb:
get(ok)
.post(ok)
.route_layer(RequireAuthorizationLayer::bearer("password"))
.merge(
delete_service(ServeDir::new("."))
.handle_error(|_| async { StatusCode::NOT_FOUND }),
)
.fallback((|| async { StatusCode::NOT_FOUND }).into_service())
.put(ok)
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_| async {
StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::from_secs(10))),
),
);
let app = crate::Router::new()
.route(
"/",
// use the all the things :bomb:
get(ok)
.post(ok)
.route_layer(RequireAuthorizationLayer::bearer("password"))
.merge(
delete_service(ServeDir::new("."))
.handle_error(|_| async { StatusCode::NOT_FOUND }),
)
.fallback((|| async { StatusCode::NOT_FOUND }).into_service())
.put(ok)
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_| async {
StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::from_secs(10))),
),
)
.state(());
crate::Server::bind(&"0.0.0.0:0".parse().unwrap()).serve(app.into_make_service());
}
+100 -15
View File
@@ -16,6 +16,7 @@ use std::{
collections::HashMap,
convert::Infallible,
fmt,
marker::PhantomData,
sync::Arc,
task::{Context, Poll},
};
@@ -62,23 +63,33 @@ impl RouteId {
}
/// The router type for composing handlers and services.
pub struct Router<B = Body> {
pub struct Router<S, B = Body, R = MissingState> {
// Invariant: If `R == MissingState` then `state` is `None`
// If `R == WithState` then state is `Some`
// `R` cannot have other values
state: Option<S>,
routes: HashMap<RouteId, Endpoint<B>>,
node: Arc<Node>,
fallback: Fallback<B>,
_marker: PhantomData<R>,
}
impl<B> Clone for Router<B> {
impl<S, B, R> Clone for Router<S, B, R>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
routes: self.routes.clone(),
node: Arc::clone(&self.node),
fallback: self.fallback.clone(),
_marker: PhantomData,
}
}
}
impl<B> Default for Router<B>
impl<S, B> Default for Router<S, B, MissingState>
where
B: HttpBody + Send + 'static,
{
@@ -87,12 +98,23 @@ where
}
}
impl<B> fmt::Debug for Router<B> {
impl<S, B, R> fmt::Debug for Router<S, B, R>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
state,
routes,
node,
fallback,
_marker,
} = self;
f.debug_struct("Router")
.field("routes", &self.routes)
.field("node", &self.node)
.field("fallback", &self.fallback)
.field("state", &state)
.field("routes", &routes)
.field("node", &node)
.field("fallback", &fallback)
.finish()
}
}
@@ -100,7 +122,7 @@ impl<B> fmt::Debug for Router<B> {
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param";
impl<B> Router<B>
impl<S, B> Router<S, B, MissingState>
where
B: HttpBody + Send + 'static,
{
@@ -110,12 +132,42 @@ where
/// all requests.
pub fn new() -> Self {
Self {
state: None,
routes: Default::default(),
node: Default::default(),
fallback: Fallback::Default(Route::new(NotFound)),
_marker: PhantomData,
}
}
/// TODO(david): docs
pub fn state(self, state: S) -> Router<S, B, WithState> {
Router {
state: Some(state),
routes: self.routes,
node: self.node,
fallback: self.fallback,
_marker: PhantomData,
}
}
}
impl<S, B> Router<S, B, WithState>
where
B: HttpBody + Send + 'static,
{
/// TODO(david): docs
pub fn with_state(state: S) -> Self {
Router::new().state(state)
}
}
impl<S, B, R> Router<S, B, R>
where
B: HttpBody + Send + 'static,
S: 'static,
R: 'static,
{
#[doc = include_str!("../docs/routing/route.md")]
pub fn route<T>(mut self, path: &str, service: T) -> Self
where
@@ -128,7 +180,10 @@ where
panic!("Paths must start with a `/`");
}
let service = match try_downcast::<Router<B>, _>(service) {
// 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")
}
@@ -171,7 +226,7 @@ where
}
#[doc = include_str!("../docs/routing/nest.md")]
pub fn nest(mut self, mut path: &str, router: Router<B>) -> Self {
pub fn nest(mut self, mut path: &str, router: Router<S, B, MissingState>) -> Self {
if path.is_empty() {
// nesting at `""` and `"/"` should mean the same thing
path = "/";
@@ -184,11 +239,15 @@ where
let prefix = path;
let Router {
state,
mut routes,
node,
fallback,
_marker: _,
} = router;
debug_assert!(state.is_none());
if let Fallback::Custom(_) = fallback {
panic!("Cannot nest `Router`s that has a fallback");
}
@@ -255,16 +314,20 @@ where
}
#[doc = include_str!("../docs/routing/merge.md")]
pub fn merge<R>(mut self, other: R) -> Self
pub fn merge<R2>(mut self, other: R2) -> Self
where
R: Into<Router<B>>,
R2: Into<Router<S, B, MissingState>>,
{
let Router {
state,
routes,
node,
fallback,
_marker: _,
} = other.into();
debug_assert!(state.is_none());
for (id, route) in routes {
let path = node
.route_id_to_path
@@ -289,7 +352,7 @@ where
}
#[doc = include_str!("../docs/routing/layer.md")]
pub fn layer<L, NewReqBody, NewResBody>(self, layer: L) -> Router<NewReqBody>
pub fn layer<L, NewReqBody, NewResBody>(self, layer: L) -> Router<S, NewReqBody, R>
where
L: Layer<Route<B>>,
L::Service:
@@ -322,9 +385,11 @@ where
let fallback = self.fallback.map(|svc| Route::new(layer.layer(svc)));
Router {
state: self.state,
routes,
node: self.node,
fallback,
_marker: self._marker,
}
}
@@ -359,9 +424,11 @@ where
.collect();
Router {
state: self.state,
routes,
node: self.node,
fallback: self.fallback,
_marker: self._marker,
}
}
@@ -406,7 +473,13 @@ where
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
IntoMakeServiceWithConnectInfo::new(self)
}
}
impl<S, B> Router<S, B, WithState>
where
B: HttpBody + Send + 'static,
S: Clone + Send + Sync + 'static,
{
#[inline]
fn call_route(
&self,
@@ -442,6 +515,10 @@ where
url_params::insert_url_params(req.extensions_mut(), match_.params);
// the `unwrap` is safe because `self.state` is always some if `R = WithState`, which it is
req.extensions_mut()
.insert(crate::extract::State(self.state.as_ref().unwrap().clone()));
let mut route = self
.routes
.get(&id)
@@ -455,9 +532,10 @@ where
}
}
impl<B> Service<Request<B>> for Router<B>
impl<S, B> Service<Request<B>> for Router<S, B, WithState>
where
B: HttpBody + Send + 'static,
S: Clone + Send + Sync + 'static,
{
type Response = Response;
type Error = Infallible;
@@ -496,6 +574,12 @@ where
}
}
#[derive(Copy, Clone, Debug)]
pub enum MissingState {}
#[derive(Copy, Clone, Debug)]
pub enum WithState {}
/// Wrapper around `matchit::Router` that supports merging two `Router`s.
#[derive(Clone, Default)]
struct Node {
@@ -599,5 +683,6 @@ impl<B> fmt::Debug for Endpoint<B> {
#[allow(warnings)]
fn traits() {
use crate::test_helpers::*;
assert_send::<Router<()>>();
assert_send::<Router<(), (), WithState>>();
assert_send::<Router<(), (), MissingState>>();
}
+6 -3
View File
@@ -5,7 +5,8 @@ use crate::handler::Handler;
async fn basic() {
let app = Router::new()
.route("/foo", get(|| async {}))
.fallback((|| async { "fallback" }).into_service());
.fallback((|| async { "fallback" }).into_service())
.state(());
let client = TestClient::new(app);
@@ -20,7 +21,8 @@ async fn basic() {
async fn nest() {
let app = Router::new()
.nest("/foo", Router::new().route("/bar", get(|| async {})))
.fallback((|| async { "fallback" }).into_service());
.fallback((|| async { "fallback" }).into_service())
.state(());
let client = TestClient::new(app);
@@ -38,7 +40,8 @@ async fn or() {
let app = one
.merge(two)
.fallback((|| async { "fallback" }).into_service());
.fallback((|| async { "fallback" }).into_service())
.state(());
let client = TestClient::new(app);
+20 -16
View File
@@ -8,14 +8,16 @@ mod for_handlers {
#[tokio::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",
get(|| async {
let mut headers = HeaderMap::new();
headers.insert("x-some-header", "foobar".parse().unwrap());
(headers, "you shouldn't see this")
}),
);
let app = Router::new()
.route(
"/",
get(|| async {
let mut headers = HeaderMap::new();
headers.insert("x-some-header", "foobar".parse().unwrap());
(headers, "you shouldn't see this")
}),
)
.state(());
// don't use reqwest because it always strips bodies from HEAD responses
let res = app
@@ -43,14 +45,16 @@ mod for_services {
#[tokio::test]
async fn get_handles_head() {
let app = Router::new().route(
"/",
get_service(service_fn(|_req: Request<Body>| async move {
Ok::<_, Infallible>(
([("x-some-header", "foobar")], "you shouldn't see this").into_response(),
)
})),
);
let app = Router::new()
.route(
"/",
get_service(service_fn(|_req: Request<Body>| async move {
Ok::<_, Infallible>(
([("x-some-header", "foobar")], "you shouldn't see this").into_response(),
)
})),
)
.state(());
// don't use reqwest because it always strips bodies from HEAD responses
let res = app
+4 -4
View File
@@ -31,7 +31,7 @@ impl<R> Service<R> for Svc {
#[tokio::test]
async fn handler() {
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
get(forever.layer(
ServiceBuilder::new()
@@ -50,7 +50,7 @@ async fn handler() {
#[tokio::test]
async fn handler_multiple_methods_first() {
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
get(forever.layer(
ServiceBuilder::new()
@@ -70,7 +70,7 @@ async fn handler_multiple_methods_first() {
#[tokio::test]
async fn handler_multiple_methods_middle() {
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
delete(unit)
.get(
@@ -93,7 +93,7 @@ async fn handler_multiple_methods_middle() {
#[tokio::test]
async fn handler_multiple_methods_last() {
let app = Router::new().route(
let app = Router::with_state(()).route(
"/",
delete(unit).get(
forever.layer(
+25 -19
View File
@@ -12,7 +12,7 @@ async fn basic() {
.route("/foo", get(|| async {}))
.route("/bar", get(|| async {}));
let two = Router::new().route("/baz", get(|| async {}));
let app = one.merge(two);
let app = one.merge(two).state(());
let client = TestClient::new(app);
@@ -41,7 +41,8 @@ async fn multiple_ors_balanced_differently() {
one.clone()
.merge(two.clone())
.merge(three.clone())
.merge(four.clone()),
.merge(four.clone())
.state(()),
)
.await;
@@ -49,18 +50,20 @@ async fn multiple_ors_balanced_differently() {
"two",
one.clone()
.merge(two.clone())
.merge(three.clone().merge(four.clone())),
.merge(three.clone().merge(four.clone()))
.state(()),
)
.await;
test(
"three",
one.clone()
.merge(two.clone().merge(three.clone()).merge(four.clone())),
.merge(two.clone().merge(three.clone()).merge(four.clone()))
.state(()),
)
.await;
test("four", one.merge(two.merge(three.merge(four)))).await;
test("four", one.merge(two.merge(three.merge(four))).state(())).await;
async fn test<S, ResBody>(name: &str, app: S)
where
@@ -89,11 +92,11 @@ async fn nested_or() {
let bar_or_baz = bar.merge(baz);
let client = TestClient::new(bar_or_baz.clone());
let client = TestClient::new(bar_or_baz.clone().state(()));
assert_eq!(client.get("/bar").send().await.text().await, "bar");
assert_eq!(client.get("/baz").send().await.text().await, "baz");
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz));
let client = TestClient::new(Router::new().nest("/foo", bar_or_baz).state(()));
assert_eq!(client.get("/foo/bar").send().await.text().await, "bar");
assert_eq!(client.get("/foo/baz").send().await.text().await, "baz");
}
@@ -102,7 +105,10 @@ async fn nested_or() {
async fn or_with_route_following() {
let one = Router::new().route("/one", get(|| async { "one" }));
let two = Router::new().route("/two", get(|| async { "two" }));
let app = one.merge(two).route("/three", get(|| async { "three" }));
let app = one
.merge(two)
.route("/three", get(|| async { "three" }))
.state(());
let client = TestClient::new(app);
@@ -122,7 +128,7 @@ async fn layer() {
let two = Router::new()
.route("/bar", get(|| async {}))
.layer(ConcurrencyLimitLayer::new(10));
let app = one.merge(two);
let app = one.merge(two).state(());
let client = TestClient::new(app);
@@ -145,7 +151,7 @@ async fn layer_and_handle_error() {
}))
.layer(TimeoutLayer::new(Duration::from_millis(10))),
);
let app = one.merge(two);
let app = one.merge(two).state(());
let client = TestClient::new(app);
@@ -159,7 +165,7 @@ async fn nesting() {
let two = Router::new().nest("/bar", Router::new().route("/baz", get(|| async {})));
let app = one.merge(two);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -171,7 +177,7 @@ async fn boxed() {
let two = Router::new().route("/bar", get(|| async {}));
let app = one.merge(two);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -188,7 +194,7 @@ async fn many_ors() {
.merge(Router::new().route("/r6", get(|| async {})))
.merge(Router::new().route("/r7", get(|| async {})));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
for n in 1..=7 {
let res = client.get(&format!("/r{}", n)).send().await;
@@ -217,7 +223,7 @@ async fn services() {
})),
));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -243,7 +249,7 @@ async fn nesting_and_seeing_the_right_uri() {
let one = Router::new().nest("/foo", Router::new().route("/bar", get(all_the_uris)));
let two = Router::new().route("/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let client = TestClient::new(one.merge(two).state(()));
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -276,7 +282,7 @@ async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() {
);
let two = Router::new().route("/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let client = TestClient::new(one.merge(two).state(()));
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -310,7 +316,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_nesting() {
let two = Router::new().nest("/two", Router::new().route("/qux", get(all_the_uris)));
let three = Router::new().route("/three", get(all_the_uris));
let client = TestClient::new(one.merge(two).merge(three));
let client = TestClient::new(one.merge(two).merge(three).state(()));
let res = client.get("/one/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -354,7 +360,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() {
);
let two = Router::new().route("/two/foo", get(all_the_uris));
let client = TestClient::new(one.merge(two));
let client = TestClient::new(one.merge(two).state(()));
let res = client.get("/one/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -387,7 +393,7 @@ async fn middleware_that_return_early() {
let public = Router::new().route("/public", get(|| async {}));
let client = TestClient::new(private.merge(public));
let client = TestClient::new(private.merge(public).state(()));
assert_eq!(
client.get("/").send().await.status(),
+32 -32
View File
@@ -46,7 +46,7 @@ async fn hello_world() {
.route("/", get(root).post(foo))
.route("/users", post(users_create));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
let body = res.text().await;
@@ -75,7 +75,7 @@ async fn routing() {
get(|_: Request<Body>| async { "users#action" }),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -99,7 +99,7 @@ async fn routing() {
#[tokio::test]
async fn router_type_doesnt_change() {
let app: Router = Router::new()
let app: Router<()> = Router::new()
.route(
"/",
on(MethodFilter::GET, |_: Request<Body>| async {
@@ -111,7 +111,7 @@ async fn router_type_doesnt_change() {
)
.layer(tower_http::compression::CompressionLayer::new());
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -149,7 +149,7 @@ async fn routing_between_services() {
)
.route("/two", on_service(MethodFilter::GET, handle.into_service()));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/one").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -187,7 +187,7 @@ async fn middleware_on_single_route() {
)),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
let body = res.text().await;
@@ -203,7 +203,7 @@ async fn service_in_bottom() {
let app = Router::new().route("/", get_service(service_fn(handler)));
TestClient::new(app);
TestClient::new(app.state(()));
}
#[tokio::test]
@@ -212,7 +212,7 @@ async fn wrong_method_handler() {
.route("/", get(|| async {}).post(|| async {}))
.route("/foo", patch(|| async {}));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.patch("/").send().await;
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
@@ -250,7 +250,7 @@ async fn wrong_method_service() {
.route("/", get_service(Svc).post_service(Svc))
.route("/foo", patch_service(Svc));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.patch("/").send().await;
assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
@@ -273,7 +273,7 @@ async fn multiple_methods_for_one_handler() {
let app = Router::new().route("/", on(MethodFilter::GET | MethodFilter::POST, root));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -286,7 +286,7 @@ async fn multiple_methods_for_one_handler() {
async fn wildcard_sees_whole_url() {
let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/api/foo/bar").send().await;
assert_eq!(res.text().await, "/api/foo/bar");
@@ -305,7 +305,7 @@ async fn middleware_applies_to_routes_above() {
)
.route("/two", get(|| async {}));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/one").send().await;
assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
@@ -318,7 +318,7 @@ async fn middleware_applies_to_routes_above() {
async fn not_found_for_extra_trailing_slash() {
let app = Router::new().route("/foo", get(|| async {}));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -331,7 +331,7 @@ async fn not_found_for_extra_trailing_slash() {
async fn not_found_for_missing_trailing_slash() {
let app = Router::new().route("/foo/", get(|| async {}));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -343,7 +343,7 @@ async fn with_and_without_trailing_slash() {
.route("/foo", get(|| async { "without tsr" }))
.route("/foo/", get(|| async { "with tsr" }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -362,7 +362,7 @@ async fn wildcard_doesnt_match_just_trailing_slash() {
get(|Path(path): Path<String>| async move { path }),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/x").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -384,7 +384,7 @@ async fn static_and_dynamic_paths() {
)
.route("/foo", get(|| async { "static" }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/bar").send().await;
assert_eq!(res.text().await, "dynamic: bar");
@@ -397,7 +397,7 @@ async fn static_and_dynamic_paths() {
#[should_panic(expected = "Paths must start with a `/`. Use \"/\" for root routes")]
async fn empty_route() {
let app = Router::new().route("", get(|| async {}));
TestClient::new(app);
TestClient::new(app.state(()));
}
#[tokio::test]
@@ -429,7 +429,7 @@ async fn middleware_still_run_for_unmatched_requests() {
.route("/", get(|| async {}))
.layer(tower::layer::layer_fn(CountMiddleware));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(COUNT.load(Ordering::SeqCst), 0);
@@ -445,7 +445,7 @@ async fn middleware_still_run_for_unmatched_requests() {
expected = "Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead"
)]
async fn routing_to_router_panics() {
TestClient::new(Router::new().route("/", Router::new()));
TestClient::new(Router::new().route("/", Router::new().state(())).state(()));
}
#[tokio::test]
@@ -454,7 +454,7 @@ async fn route_layer() {
.route("/foo", get(|| async {}))
.route_layer(RequireAuthorizationLayer::bearer("password"));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client
.get("/foo")
@@ -482,7 +482,7 @@ async fn different_methods_added_in_different_routes() {
.route("/", get(|| async { "GET" }))
.route("/", post(|| async { "POST" }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
let body = res.text().await;
@@ -505,7 +505,7 @@ async fn different_methods_added_in_different_routes_deeply_nested() {
),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/bar/baz").send().await;
let body = res.text().await;
@@ -522,7 +522,7 @@ async fn merging_routers_with_fallbacks_panics() {
async fn fallback() {}
let one = Router::new().fallback(fallback.into_service());
let two = Router::new().fallback(fallback.into_service());
TestClient::new(one.merge(two));
TestClient::new(one.merge(two).state(()));
}
#[tokio::test]
@@ -531,7 +531,7 @@ async fn nesting_router_with_fallbacks_panics() {
async fn fallback() {}
let one = Router::new().fallback(fallback.into_service());
let app = Router::new().nest("/", one);
TestClient::new(app);
TestClient::new(app.state(()));
}
#[tokio::test]
@@ -539,7 +539,7 @@ async fn merging_routers_with_same_paths_but_different_methods() {
let one = Router::new().route("/", get(|| async { "GET" }));
let two = Router::new().route("/", post(|| async { "POST" }));
let client = TestClient::new(one.merge(two));
let client = TestClient::new(one.merge(two).state(()));
let res = client.get("/").send().await;
let body = res.text().await;
@@ -556,7 +556,7 @@ async fn head_content_length_through_hyper_server() {
.route("/", get(|| async { "foo" }))
.route("/json", get(|| async { Json(json!({ "foo": 1 })) }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.head("/").send().await;
assert_eq!(res.headers()["content-length"], "3");
@@ -571,7 +571,7 @@ async fn head_content_length_through_hyper_server() {
async fn head_content_length_through_hyper_server_that_hits_fallback() {
let app = Router::new().fallback((|| async { "foo" }).into_service());
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.head("/").send().await;
assert_eq!(res.headers()["content-length"], "3");
@@ -585,7 +585,7 @@ async fn head_with_middleware_applied() {
.route("/", get(|| async { "Hello, World!" }))
.layer(CompressionLayer::new().compress_when(SizeAbove::new(0)));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
// send GET request
let res = client
@@ -614,7 +614,7 @@ async fn head_with_middleware_applied() {
#[should_panic(expected = "Paths must start with a `/`")]
async fn routes_must_start_with_slash() {
let app = Router::new().route(":foo", get(|| async {}));
TestClient::new(app);
TestClient::new(app.state(()));
}
#[tokio::test]
@@ -630,7 +630,7 @@ async fn limited_body_with_content_length() {
)
.layer(RequestBodyLimitLayer::new(LIMIT));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.post("/").body("a".repeat(LIMIT)).send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -652,7 +652,7 @@ async fn limited_body_with_streaming_body() {
)
.layer(RequestBodyLimitLayer::new(LIMIT));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let stream = futures_util::stream::iter(vec![Ok::<_, hyper::Error>("a".repeat(LIMIT))]);
let res = client
+21 -20
View File
@@ -40,7 +40,7 @@ async fn nesting_apps() {
.route("/", get(|| async { "hi" }))
.nest("/:version/api", api_routes);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -64,7 +64,7 @@ async fn wrong_method_nest() {
let nested_app = Router::new().route("/", get(|| async {}));
let app = Router::new().nest("/", nested_app);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -81,7 +81,7 @@ async fn nesting_router_at_root() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("/", nested);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -99,7 +99,7 @@ async fn nesting_router_at_empty_path() {
let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() }));
let app = Router::new().nest("", nested);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::NOT_FOUND);
@@ -116,7 +116,7 @@ async fn nesting_router_at_empty_path() {
async fn nesting_handler_at_root() {
let app = Router::new().nest_service("/", get(|uri: Uri| async move { uri.to_string() }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -146,7 +146,7 @@ async fn nested_url_extractor() {
),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -170,7 +170,7 @@ async fn nested_url_original_extractor() {
),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -193,7 +193,7 @@ async fn nested_service_sees_stripped_uri() {
),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/bar/baz").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -212,7 +212,7 @@ async fn nest_static_file_server() {
}),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/static/README.md").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -229,7 +229,7 @@ async fn nested_multiple_routes() {
)
.route("/", get(|| async { "root" }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(client.get("/").send().await.text().await, "root");
assert_eq!(client.get("/api/users").send().await.text().await, "users");
@@ -245,7 +245,7 @@ async fn nested_with_other_route_also_matching_with_route_first() {
.route("/teams", get(|| async { "teams" })),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(client.get("/api").send().await.text().await, "api");
assert_eq!(client.get("/api/users").send().await.text().await, "users");
@@ -263,7 +263,7 @@ async fn nested_with_other_route_also_matching_with_route_last() {
)
.route("/api", get(|| async { "api" }));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(client.get("/api").send().await.text().await, "api");
assert_eq!(client.get("/api/users").send().await.text().await, "users");
@@ -282,7 +282,7 @@ async fn multiple_top_level_nests() {
Router::new().route("/route", get(|| async { "two" })),
);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(client.get("/one/route").send().await.text().await, "one");
assert_eq!(client.get("/two/route").send().await.text().await, "two");
@@ -291,7 +291,7 @@ async fn multiple_top_level_nests() {
#[tokio::test]
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
async fn nest_cannot_contain_wildcards() {
Router::<Body>::new().nest("/one/*rest", Router::new());
Router::<(), Body, _>::new().nest("/one/*rest", Router::new());
}
#[tokio::test]
@@ -333,7 +333,7 @@ async fn outer_middleware_still_see_whole_url() {
.fallback(handler.into_service())
.layer(tower::layer::layer_fn(SetUriExtension));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
assert_eq!(client.get("/").send().await.text().await, "/");
assert_eq!(client.get("/foo").send().await.text().await, "/foo");
@@ -352,11 +352,12 @@ async fn nest_at_capture() {
"/:b",
get(|Path((a, b)): Path<(String, String)>| async move { format!("a={} b={}", a, b) }),
)
.state(())
.boxed_clone();
let app = Router::new().nest_service("/:a", api_routes);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo/bar").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -367,7 +368,7 @@ async fn nest_at_capture() {
async fn nest_with_and_without_trailing() {
let app = Router::new().nest_service("/foo", get(|| async {}));
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get("/foo").send().await;
assert_eq!(res.status(), StatusCode::OK);
@@ -393,14 +394,14 @@ macro_rules! nested_route_test {
async fn $name() {
let inner = Router::new().route($route_path, get(|| async {}));
let app = Router::new().nest($nested_path, inner);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get($expected_path).send().await;
let status = res.status();
assert_eq!(status, StatusCode::OK, "Router");
let inner = Router::new().route($route_path, get(|| async {}));
let inner = Router::new().route($route_path, get(|| async {})).state(());
let app = Router::new().nest_service($nested_path, inner);
let client = TestClient::new(app);
let client = TestClient::new(app.state(()));
let res = client.get(dbg!($expected_path)).send().await;
assert_eq!(res.status(), StatusCode::OK, "opaque");
}
+1 -1
View File
@@ -177,7 +177,7 @@ mod tests {
user_agent.to_string()
}
let app = Router::new().route("/", get(handle));
let app = Router::new().route("/", get(handle)).state(());
let client = TestClient::new(app);