Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type safe state inheritance #1532

Merged
merged 16 commits into from
Nov 18, 2022
8 changes: 4 additions & 4 deletions axum-extra/src/extract/cookie/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,11 @@ mod tests {
custom_key: CustomKey(Key::generate()),
};

let app = Router::<_, Body>::with_state(state)
let app = Router::<_, Body>::new()
.route("/set", get(set_cookie))
.route("/get", get(get_cookie))
.route("/remove", get(remove_cookie))
.into_service();
.into_service(state);

let res = app
.clone()
Expand Down Expand Up @@ -352,9 +352,9 @@ mod tests {
custom_key: CustomKey(Key::generate()),
};

let app = Router::<_, Body>::with_state(state)
let app = Router::<_, Body>::new()
.route("/get", get(get_cookie))
.into_service();
.into_service(state);

let res = app
.clone()
Expand Down
20 changes: 4 additions & 16 deletions axum-extra/src/routing/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,18 @@ pub struct Resource<S = (), B = Body> {
pub(crate) router: Router<S, B>,
}

impl<B> Resource<(), B>
where
B: axum::body::HttpBody + Send + 'static,
{
/// Create a `Resource` with the given name.
///
/// 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.
/// Create a `Resource` with the given name.
///
/// All routes will be nested at `/{resource_name}`.
pub fn named_with(state: S, resource_name: &str) -> Self {
pub fn named(resource_name: &str) -> Self {
Self {
name: resource_name.to_owned(),
router: Router::with_state(state),
router: Router::new(),
}
}

Expand Down Expand Up @@ -174,7 +162,7 @@ mod tests {
.update(|Path(id): Path<u64>| async move { format!("users#update id={}", id) })
.destroy(|Path(id): Path<u64>| async move { format!("users#destroy id={}", id) });

let mut app = Router::new().merge(users).into_service();
let mut app = Router::new().merge(users).into_service(());

assert_eq!(
call_route(&mut app, Method::GET, "/users").await,
Expand Down
24 changes: 13 additions & 11 deletions axum-extra/src/routing/spa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ use tower_service::Service;
/// - `GET /some/other/path` will serve `index.html` since there isn't another
/// route for it
/// - `GET /api/foo` will serve the `api_foo` handler function
pub struct SpaRouter<B = Body, T = (), F = fn(io::Error) -> Ready<StatusCode>> {
pub struct SpaRouter<S = (), B = Body, T = (), F = fn(io::Error) -> Ready<StatusCode>> {
paths: Arc<Paths>,
handle_error: F,
_marker: PhantomData<fn() -> (B, T)>,
_marker: PhantomData<fn() -> (S, B, T)>,
}

#[derive(Debug)]
Expand All @@ -63,7 +63,7 @@ struct Paths {
index_file: PathBuf,
}

impl<B> SpaRouter<B, (), fn(io::Error) -> Ready<StatusCode>> {
impl<S, B> SpaRouter<S, B, (), fn(io::Error) -> Ready<StatusCode>> {
/// Create a new `SpaRouter`.
///
/// Assets will be served at `GET /{serve_assets_at}` from the directory at `assets_dir`.
Expand All @@ -86,7 +86,7 @@ impl<B> SpaRouter<B, (), fn(io::Error) -> Ready<StatusCode>> {
}
}

impl<B, T, F> SpaRouter<B, T, F> {
impl<S, B, T, F> SpaRouter<S, B, T, F> {
/// Set the path to the index file.
///
/// `path` must be relative to `assets_dir` passed to [`SpaRouter::new`].
Expand Down Expand Up @@ -138,7 +138,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
/// let app = Router::new().merge(spa);
/// # let _: Router = app;
/// ```
pub fn handle_error<T2, F2>(self, f: F2) -> SpaRouter<B, T2, F2> {
pub fn handle_error<T2, F2>(self, f: F2) -> SpaRouter<S, B, T2, F2> {
SpaRouter {
paths: self.paths,
handle_error: f,
Expand All @@ -147,16 +147,17 @@ impl<B, T, F> SpaRouter<B, T, F> {
}
}

impl<B, F, T> From<SpaRouter<B, T, F>> for Router<(), B>
impl<S, B, F, T> From<SpaRouter<S, B, T, F>> for Router<S, B>
where
F: Clone + Send + Sync + 'static,
HandleError<Route<B, io::Error>, F, T>: Service<Request<B>, Error = Infallible>,
<HandleError<Route<B, io::Error>, F, T> as Service<Request<B>>>::Response: IntoResponse + Send,
<HandleError<Route<B, io::Error>, F, T> as Service<Request<B>>>::Future: Send,
B: HttpBody + Send + 'static,
T: 'static,
S: Clone + Send + Sync + 'static,
{
fn from(spa: SpaRouter<B, T, F>) -> Self {
fn from(spa: SpaRouter<S, B, T, F>) -> Router<S, B> {
let assets_service = get_service(ServeDir::new(&spa.paths.assets_dir))
.handle_error(spa.handle_error.clone());

Expand Down Expand Up @@ -195,7 +196,7 @@ where
fn clone(&self) -> Self {
Self {
paths: self.paths.clone(),
handle_error: self.handle_error.clone(),
handle_error: self.handle_error,
_marker: self._marker,
}
}
Expand Down Expand Up @@ -264,13 +265,14 @@ 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())
let _: axum::RouterService = Router::new()
.merge(SpaRouter::new("/assets", "test_files"))
.route("/", get(|_: axum::extract::State<String>| async {}));
.route("/", get(|_: axum::extract::State<String>| async {}))
.into_service(String::new());
}
}
53 changes: 33 additions & 20 deletions axum/benches/benches.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use axum::{
extract::State,
routing::{get, post},
Extension, Json, Router, Server,
Extension, Json, Router, RouterService, Server,
};
use hyper::server::conn::AddrIncoming;
use serde::{Deserialize, Serialize};
Expand All @@ -17,9 +17,13 @@ fn main() {
ensure_rewrk_is_installed();
}

benchmark("minimal").run(Router::new);
benchmark("minimal").run(|| Router::new().into_service(()));

benchmark("basic").run(|| Router::new().route("/", get(|| async { "Hello, World!" })));
benchmark("basic").run(|| {
Router::new()
.route("/", get(|| async { "Hello, World!" }))
.into_service(())
});

benchmark("routing").path("/foo/bar/baz").run(|| {
let mut app = Router::new();
Expand All @@ -30,26 +34,32 @@ fn main() {
}
}
}
app.route("/foo/bar/baz", get(|| async {}))
app.route("/foo/bar/baz", get(|| async {})).into_service(())
});

benchmark("receive-json")
.method("post")
.headers(&[("content-type", "application/json")])
.body(r#"{"n": 123, "s": "hi there", "b": false}"#)
.run(|| Router::new().route("/", post(|_: Json<Payload>| async {})));
.run(|| {
Router::new()
.route("/", post(|_: Json<Payload>| async {}))
.into_service(())
});

benchmark("send-json").run(|| {
Router::new().route(
"/",
get(|| async {
Json(Payload {
n: 123,
s: "hi there".to_owned(),
b: false,
})
}),
)
Router::new()
.route(
"/",
get(|| async {
Json(Payload {
n: 123,
s: "hi there".to_owned(),
b: false,
})
}),
)
.into_service(())
});

let state = AppState {
Expand All @@ -65,10 +75,14 @@ fn main() {
Router::new()
.route("/", get(|_: Extension<AppState>| async {}))
.layer(Extension(state.clone()))
.into_service(())
});

benchmark("state")
.run(|| Router::with_state(state.clone()).route("/", get(|_: State<AppState>| async {})));
benchmark("state").run(|| {
Router::new()
.route("/", get(|_: State<AppState>| async {}))
.into_service(state.clone())
});
}

#[derive(Clone)]
Expand Down Expand Up @@ -117,10 +131,9 @@ impl BenchmarkBuilder {
config_method!(headers, &'static [(&'static str, &'static str)]);
config_method!(body, &'static str);

fn run<F, S>(self, f: F)
fn run<F>(self, f: F)
where
F: FnOnce() -> Router<S>,
S: Clone + Send + Sync + 'static,
F: FnOnce() -> RouterService,
{
// support only running some benchmarks with
// ```
Expand Down