Type safe state inheritance (#1532)

* Make state type safe

* fix examples

* remove unnecessary `#[track_caller]`s

* Router::into_service -> Router::with_state

* fixup docs

* macro docs

* add missing docs

* fix examples

* format

* changelog

* Update trybuild tests

* Make sure fallbacks are still inherited for opaque services (#1540)

* Document nesting routers with different state

* fix leftover conflicts
This commit is contained in:
David Pedersen
2022-11-18 11:02:58 +00:00
committed by GitHub
parent ba8e9c1b21
commit 64960bb19c
62 changed files with 675 additions and 736 deletions
+4 -4
View File
@@ -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();
.with_state(state);
let res = app
.clone()
@@ -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();
.with_state(state);
let res = app
.clone()
+4 -3
View File
@@ -64,10 +64,11 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
/// key: Key::generate(),
/// };
///
/// let app = Router::with_state(state)
/// let app = Router::new()
/// .route("/set", post(set_secret))
/// .route("/get", get(get_secret));
/// # let app: Router<_> = app;
/// .route("/get", get(get_secret))
/// .with_state(state);
/// # let _: axum::routing::RouterService = app;
/// ```
pub struct PrivateCookieJar<K = Key> {
jar: cookie::CookieJar,
+4 -3
View File
@@ -82,10 +82,11 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
/// key: Key::generate(),
/// };
///
/// let app = Router::with_state(state)
/// let app = Router::new()
/// .route("/sessions", post(create_session))
/// .route("/me", get(me));
/// # let app: Router<_> = app;
/// .route("/me", get(me))
/// .with_state(state);
/// # let _: axum::routing::RouterService = app;
/// ```
pub struct SignedCookieJar<K = Key> {
jar: cookie::CookieJar,
+3 -15
View File
@@ -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(),
}
}
+13 -11
View File
@@ -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)]
@@ -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`.
@@ -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`].
@@ -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,
@@ -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<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>,
@@ -155,8 +155,9 @@ where
<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());
@@ -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,
}
}
@@ -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 {}))
.with_state(String::new());
}
}