From add13fd6a40dcf8770ef192bbb942f2c48d150ae Mon Sep 17 00:00:00 2001 From: Matt Crane Date: Sun, 14 May 2023 03:37:39 -0700 Subject: [PATCH] change: migrate Axum integration to use `with_state` over `layer(Extension)` (#1032) --- examples/errors_axum/Cargo.toml | 8 +-- examples/errors_axum/src/fallback.rs | 4 +- examples/errors_axum/src/main.rs | 8 +-- examples/hackernews_axum/src/fallback.rs | 4 +- examples/hackernews_axum/src/main.rs | 5 +- examples/session_auth_axum/src/fallback.rs | 4 +- examples/session_auth_axum/src/main.rs | 10 ++-- examples/ssr_modes_axum/src/fallback.rs | 4 +- examples/ssr_modes_axum/src/main.rs | 6 +- examples/todo_app_sqlite_axum/src/fallback.rs | 4 +- examples/todo_app_sqlite_axum/src/main.rs | 8 +-- integrations/axum/src/lib.rs | 56 +++++++++++++++---- 12 files changed, 76 insertions(+), 45 deletions(-) diff --git a/examples/errors_axum/Cargo.toml b/examples/errors_axum/Cargo.toml index 4a3936273..2e6547805 100644 --- a/examples/errors_axum/Cargo.toml +++ b/examples/errors_axum/Cargo.toml @@ -10,12 +10,12 @@ crate-type = ["cdylib", "rlib"] console_log = "1.0.0" console_error_panic_hook = "0.1.7" cfg-if = "1.0.0" -leptos = { path = "../../../leptos/leptos", default-features = false, features = [ +leptos = { path = "../../leptos", default-features = false, features = [ "serde", ] } -leptos_axum = { path = "../../../leptos/integrations/axum", default-features = false, optional = true } -leptos_meta = { path = "../../../leptos/meta", default-features = false } -leptos_router = { path = "../../../leptos/router", default-features = false } +leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true } +leptos_meta = { path = "../../meta", default-features = false } +leptos_router = { path = "../../router", default-features = false } log = "0.4.17" serde = { version = "1", features = ["derive"] } simple_logger = "4.0.0" diff --git a/examples/errors_axum/src/fallback.rs b/examples/errors_axum/src/fallback.rs index 354beda22..90b0cbd5a 100644 --- a/examples/errors_axum/src/fallback.rs +++ b/examples/errors_axum/src/fallback.rs @@ -3,7 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, - extract::Extension, + extract::State, response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; @@ -14,7 +14,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use leptos::{LeptosOptions, view}; use crate::landing::App; - pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { + pub async fn file_and_error_handler(uri: Uri, State(options): State>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); let res = get_static_file(uri.clone(), &root).await.unwrap(); diff --git a/examples/errors_axum/src/main.rs b/examples/errors_axum/src/main.rs index d4da2d1bc..149fccff1 100644 --- a/examples/errors_axum/src/main.rs +++ b/examples/errors_axum/src/main.rs @@ -5,7 +5,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use crate::landing::*; use axum::body::Body as AxumBody; use axum::{ - extract::{Extension, Path}, + extract::{State, Path}, http::Request, response::{IntoResponse, Response}, routing::{get, post}, @@ -21,7 +21,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { #[cfg(feature = "ssr")] async fn custom_handler( Path(id): Path, - Extension(options): Extension>, + State(options): State>, req: Request, ) -> Response { let handler = leptos_axum::render_app_to_stream_with_context( @@ -44,7 +44,7 @@ async fn main() { // Setting this to None means we'll be using cargo-leptos and its env vars let conf = get_configuration(None).await.unwrap(); - let leptos_options = conf.leptos_options; + let leptos_options = Arc::new(conf.leptos_options); let addr = leptos_options.site_addr; let routes = generate_route_list(|cx| view! { cx, }).await; @@ -58,7 +58,7 @@ async fn main() { |cx| view! { cx, }, ) .fallback(file_and_error_handler) - .layer(Extension(Arc::new(leptos_options))); + .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/examples/hackernews_axum/src/fallback.rs b/examples/hackernews_axum/src/fallback.rs index bf28d36d4..bb3ad7949 100644 --- a/examples/hackernews_axum/src/fallback.rs +++ b/examples/hackernews_axum/src/fallback.rs @@ -4,7 +4,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, - extract::Extension, + extract::State, response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; @@ -15,7 +15,7 @@ if #[cfg(feature = "ssr")] { use leptos::{LeptosOptions}; use crate::error_template::error_template; - pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { + pub async fn file_and_error_handler(uri: Uri, State(options): State>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); let res = get_static_file(uri.clone(), &root).await.unwrap(); diff --git a/examples/hackernews_axum/src/main.rs b/examples/hackernews_axum/src/main.rs index a096a082e..193579107 100644 --- a/examples/hackernews_axum/src/main.rs +++ b/examples/hackernews_axum/src/main.rs @@ -7,7 +7,6 @@ if #[cfg(feature = "ssr")] { use axum::{ Router, routing::get, - extract::Extension, }; use leptos_axum::{generate_route_list, LeptosRoutes}; use std::sync::Arc; @@ -18,7 +17,7 @@ if #[cfg(feature = "ssr")] { use hackernews_axum::*; let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); - let leptos_options = conf.leptos_options; + let leptos_options = Arc::new(conf.leptos_options); let addr = leptos_options.site_addr; let routes = generate_route_list(|cx| view! { cx, }).await; @@ -29,7 +28,7 @@ if #[cfg(feature = "ssr")] { .route("/favicon.ico", get(file_and_error_handler)) .leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, } ) .fallback(file_and_error_handler) - .layer(Extension(Arc::new(leptos_options))); + .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/examples/session_auth_axum/src/fallback.rs b/examples/session_auth_axum/src/fallback.rs index 20a625afc..723b91788 100644 --- a/examples/session_auth_axum/src/fallback.rs +++ b/examples/session_auth_axum/src/fallback.rs @@ -4,7 +4,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, - extract::Extension, + extract::State, response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; @@ -16,7 +16,7 @@ if #[cfg(feature = "ssr")] { use crate::error_template::ErrorTemplate; use crate::errors::TodoAppError; - pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { + pub async fn file_and_error_handler(uri: Uri, State(options): State>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); let res = get_static_file(uri.clone(), &root).await.unwrap(); diff --git a/examples/session_auth_axum/src/main.rs b/examples/session_auth_axum/src/main.rs index 337b4d2ce..a1d0fbea7 100644 --- a/examples/session_auth_axum/src/main.rs +++ b/examples/session_auth_axum/src/main.rs @@ -6,7 +6,7 @@ if #[cfg(feature = "ssr")] { use axum::{ response::{Response, IntoResponse}, routing::get, - extract::{Path, Extension, RawQuery}, + extract::{Path, State, Extension, RawQuery}, http::{Request, header::HeaderMap}, body::Body as AxumBody, Router, @@ -33,7 +33,7 @@ if #[cfg(feature = "ssr")] { }, request).await } - async fn leptos_routes_handler(Extension(pool): Extension, auth_session: AuthSession, Extension(options): Extension>, req: Request) -> Response{ + async fn leptos_routes_handler(Extension(pool): Extension, auth_session: AuthSession, State(options): State>, req: Request) -> Response{ let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(), move |cx| { provide_context(cx, auth_session.clone()); @@ -68,7 +68,7 @@ if #[cfg(feature = "ssr")] { // Setting this to None means we'll be using cargo-leptos and its env vars let conf = get_configuration(None).await.unwrap(); - let leptos_options = conf.leptos_options; + let leptos_options = Arc::new(conf.leptos_options); let addr = leptos_options.site_addr; let routes = generate_route_list(|cx| view! { cx, }).await; @@ -80,8 +80,8 @@ if #[cfg(feature = "ssr")] { .layer(AuthSessionLayer::::new(Some(pool.clone())) .with_config(auth_config)) .layer(SessionLayer::new(session_store)) - .layer(Extension(Arc::new(leptos_options))) - .layer(Extension(pool)); + .layer(Extension(pool)) + .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/examples/ssr_modes_axum/src/fallback.rs b/examples/ssr_modes_axum/src/fallback.rs index 8fcbe3e83..52dc9f275 100644 --- a/examples/ssr_modes_axum/src/fallback.rs +++ b/examples/ssr_modes_axum/src/fallback.rs @@ -3,7 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, - extract::Extension, + extract::State, response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; @@ -14,7 +14,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use leptos::{LeptosOptions, view}; use crate::app::App; - pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { + pub async fn file_and_error_handler(uri: Uri, State(options): State>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); let res = get_static_file(uri.clone(), &root).await.unwrap(); diff --git a/examples/ssr_modes_axum/src/main.rs b/examples/ssr_modes_axum/src/main.rs index a2d99c65b..41764b010 100644 --- a/examples/ssr_modes_axum/src/main.rs +++ b/examples/ssr_modes_axum/src/main.rs @@ -1,7 +1,7 @@ #[cfg(feature = "ssr")] #[tokio::main] async fn main() { - use axum::{extract::Extension, routing::post, Router}; + use axum::{routing::post, Router}; use leptos::*; use leptos_axum::{generate_route_list, LeptosRoutes}; use ssr_modes_axum::{app::*, fallback::file_and_error_handler}; @@ -9,7 +9,7 @@ async fn main() { let conf = get_configuration(None).await.unwrap(); let addr = conf.leptos_options.site_addr; - let leptos_options = conf.leptos_options; + let leptos_options = Arc::new(conf.leptos_options); // Generate the list of routes in your Leptos App let routes = generate_route_list(|cx| view! { cx, }).await; @@ -24,7 +24,7 @@ async fn main() { |cx| view! { cx, }, ) .fallback(file_and_error_handler) - .layer(Extension(Arc::new(leptos_options))); + .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/examples/todo_app_sqlite_axum/src/fallback.rs b/examples/todo_app_sqlite_axum/src/fallback.rs index 20a625afc..723b91788 100644 --- a/examples/todo_app_sqlite_axum/src/fallback.rs +++ b/examples/todo_app_sqlite_axum/src/fallback.rs @@ -4,7 +4,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, - extract::Extension, + extract::State, response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; @@ -16,7 +16,7 @@ if #[cfg(feature = "ssr")] { use crate::error_template::ErrorTemplate; use crate::errors::TodoAppError; - pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { + pub async fn file_and_error_handler(uri: Uri, State(options): State>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); let res = get_static_file(uri.clone(), &root).await.unwrap(); diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index 69dda31c4..8b9509f7c 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -5,7 +5,7 @@ cfg_if! { use leptos::*; use axum::{ routing::{post, get}, - extract::{Extension, Path}, + extract::{State, Path}, http::Request, response::{IntoResponse, Response}, Router, @@ -18,7 +18,7 @@ cfg_if! { use std::sync::Arc; //Define a handler to test extractor with state - async fn custom_handler(Path(id): Path, Extension(options): Extension>, req: Request) -> Response{ + async fn custom_handler(Path(id): Path, State(options): State>, req: Request) -> Response{ let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(), move |cx| { provide_context(cx, id.clone()); @@ -42,7 +42,7 @@ cfg_if! { // Setting this to None means we'll be using cargo-leptos and its env vars let conf = get_configuration(None).await.unwrap(); - let leptos_options = conf.leptos_options; + let leptos_options = Arc::new(conf.leptos_options); let addr = leptos_options.site_addr; let routes = generate_route_list(|cx| view! { cx, }).await; @@ -52,7 +52,7 @@ cfg_if! { .route("/special/:id", get(custom_handler)) .leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, } ) .fallback(file_and_error_handler) - .layer(Extension(Arc::new(leptos_options))); + .with_state(leptos_options); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 83fef57c6..d535da754 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -1126,12 +1126,39 @@ where } } +/// This trait allows one to use your custom struct in Axum's router, provided it can provide the +/// `LeptosOptions` to use for the `LeptosRoutes` trait functions. +pub trait LeptosOptionProvider { + fn options(&self) -> LeptosOptions; +} + +/// Implement `LeptosOptionProvider` trait for `LeptosOptions` itself. +impl LeptosOptionProvider for LeptosOptions { + fn options(&self) -> LeptosOptions { + self.clone() + } +} + +/// Implement `LeptosOptionProvider` trait for any type wrapped in an Arc, if that type implements +/// `LeptosOptionProvider` as states in axum are often provided wrapped in an Arc. +impl LeptosOptionProvider for Arc +where + T: LeptosOptionProvider, +{ + fn options(&self) -> LeptosOptions { + (**self).options() + } +} + /// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid /// having to use wildcards or manually define all routes in multiple places. -pub trait LeptosRoutes { +pub trait LeptosRoutes +where + OP: LeptosOptionProvider, +{ fn leptos_routes( self, - options: LeptosOptions, + options: OP, paths: Vec, app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, ) -> Self @@ -1140,7 +1167,7 @@ pub trait LeptosRoutes { fn leptos_routes_with_context( self, - options: LeptosOptions, + options: OP, paths: Vec, additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send, app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, @@ -1154,16 +1181,20 @@ pub trait LeptosRoutes { handler: H, ) -> Self where - H: axum::handler::Handler, + H: axum::handler::Handler, T: 'static; } + /// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests /// to those paths to Leptos's renderer. -impl LeptosRoutes for axum::Router { +impl LeptosRoutes for axum::Router +where + OP: LeptosOptionProvider + Clone + Send + Sync + 'static, +{ #[tracing::instrument(level = "info", fields(error), skip_all)] fn leptos_routes( self, - options: LeptosOptions, + options: OP, paths: Vec, app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, ) -> Self @@ -1176,7 +1207,7 @@ impl LeptosRoutes for axum::Router { #[tracing::instrument(level = "trace", fields(error), skip_all)] fn leptos_routes_with_context( self, - options: LeptosOptions, + options: OP, paths: Vec, additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send, app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, @@ -1194,7 +1225,7 @@ impl LeptosRoutes for axum::Router { match listing.mode() { SsrMode::OutOfOrder => { let s = render_app_to_stream_with_context( - options.clone(), + options.options(), additional_context.clone(), app_fn.clone(), ); @@ -1208,7 +1239,7 @@ impl LeptosRoutes for axum::Router { } SsrMode::PartiallyBlocked => { let s = render_app_to_stream_with_context_and_replace_blocks( - options.clone(), + options.options(), additional_context.clone(), app_fn.clone(), true @@ -1223,7 +1254,7 @@ impl LeptosRoutes for axum::Router { } SsrMode::InOrder => { let s = render_app_to_stream_in_order_with_context( - options.clone(), + options.options(), additional_context.clone(), app_fn.clone(), ); @@ -1237,7 +1268,7 @@ impl LeptosRoutes for axum::Router { } SsrMode::Async => { let s = render_app_async_with_context( - options.clone(), + options.options(), additional_context.clone(), app_fn.clone(), ); @@ -1263,7 +1294,7 @@ impl LeptosRoutes for axum::Router { handler: H, ) -> Self where - H: axum::handler::Handler, + H: axum::handler::Handler, T: 'static, { let mut router = self; @@ -1286,6 +1317,7 @@ impl LeptosRoutes for axum::Router { router } } + #[tracing::instrument(level = "trace", fields(error), skip_all)] fn get_leptos_pool() -> LocalPoolHandle { static LOCAL_POOL: OnceCell = OnceCell::new();