From 3215e44c9a4b6f8921bb7be4e75de14c670fbc9e Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 12:59:15 -0800 Subject: [PATCH 01/13] Create example of file_and_error handler for Axum, and create for leptos --- examples/todo_app_sqlite_axum/src/file.rs | 20 ++++++++++------ examples/todo_app_sqlite_axum/src/main.rs | 4 ++-- examples/todo_app_sqlite_axum/src/todo.rs | 11 +++++++++ integrations/axum/src/lib.rs | 4 ++++ leptos/src/error_boundary.rs | 29 +++++++++++++++++++++++ leptos/src/lib.rs | 3 ++- leptos_dom/src/components.rs | 2 ++ leptos_dom/src/components/errors.rs | 12 ++++++++++ 8 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 leptos/src/error_boundary.rs create mode 100644 leptos_dom/src/components/errors.rs diff --git a/examples/todo_app_sqlite_axum/src/file.rs b/examples/todo_app_sqlite_axum/src/file.rs index 5a2439890..6db46933f 100644 --- a/examples/todo_app_sqlite_axum/src/file.rs +++ b/examples/todo_app_sqlite_axum/src/file.rs @@ -5,22 +5,26 @@ if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, extract::Extension, + response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; + use axum::response::Response as AxumResponse; use tower::ServiceExt; use tower_http::services::ServeDir; use std::sync::Arc; - use leptos::LeptosOptions; - - pub async fn file_handler(uri: Uri, Extension(options): Extension>) -> Result, (StatusCode, String)> { + use leptos::{LeptosOptions, view}; + use crate::todo::{ErrorBoundary, ErrorBoundaryProps}; + pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); - let res = get_static_file(uri.clone(), &root).await?; + let res = get_static_file(uri.clone(), &root).await.unwrap(); - match res.status() { - StatusCode::OK => Ok(res), - _ => Err((res.status(), "File Not Found".to_string())) + if res.status() == StatusCode::OK { + res.into_response() + } else{ + let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| view! { cx, }); + handler(req).await.into_response() } } @@ -37,5 +41,7 @@ if #[cfg(feature = "ssr")] { )), } } + + } } diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index 57b34edc0..d7bfec628 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -10,7 +10,7 @@ if #[cfg(feature = "ssr")] { }; use crate::todo::*; use todo_app_sqlite_axum::*; - use crate::file::file_handler; + use crate::file::file_and_error_handler; use leptos_axum::{generate_route_list, LeptosRoutes}; use std::sync::Arc; @@ -36,7 +36,7 @@ if #[cfg(feature = "ssr")] { let app = Router::new() .route("/api/*fn_name", post(leptos_axum::handle_server_fns)) .leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, } ) - .fallback(file_handler) + .fallback(file_and_error_handler) .layer(Extension(Arc::new(leptos_options))); // run our app with hyper diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 196866387..582a83869 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -105,6 +105,17 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { .map_err(|e| ServerFnError::ServerError(e.to_string())) } +#[component] +pub fn ErrorBoundary(cx: Scope) -> impl IntoView { + provide_meta_context(cx); + view! { + cx, + + +

"ERROR"

+ } +} + #[component] pub fn TodoApp(cx: Scope) -> impl IntoView { provide_meta_context(cx); diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 63613dbdf..6a5ff988a 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -485,6 +485,10 @@ where } } +// impl IntoResponse for Pin>>>>{ +// todo!() +// } + /// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically /// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element /// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs new file mode 100644 index 000000000..f702b09be --- /dev/null +++ b/leptos/src/error_boundary.rs @@ -0,0 +1,29 @@ +use leptos_dom::{Errors, Fragment, IntoView, View}; +use leptos_macro::component; +use leptos_reactive::{create_rw_signal, provide_context, RwSignal, Scope}; + +#[component(transparent)] +pub fn ErrorBoundary( + cx: Scope, + /// The components inside the tag which will get rendered + children: Box Fragment>, + /// A fallback that will be shown if an error occurs. + fallback: F, +) -> impl IntoView +where + F: Fn(Scope, RwSignal) -> View + 'static, +{ + let errors: RwSignal = create_rw_signal(cx, Errors::default()); + + provide_context(cx, errors); + + // Run children so that they render and execute resources + let children = children(cx); + + move || match errors.get().0.is_empty() { + true => children.clone(), + false => fallback(cx, errors).into(), + } +} + +// impl IntoView for Result<(), Box> {} diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index e54db0446..87f3a9a52 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -138,7 +138,8 @@ pub use leptos_server::*; pub use tracing; pub use typed_builder; - +mod error_boundary; +pub use error_boundary::*; mod for_loop; pub use for_loop::*; mod suspense; diff --git a/leptos_dom/src/components.rs b/leptos_dom/src/components.rs index 8c1d6585a..7e912b317 100644 --- a/leptos_dom/src/components.rs +++ b/leptos_dom/src/components.rs @@ -1,5 +1,6 @@ mod dyn_child; mod each; +mod errors; mod fragment; mod unit; @@ -11,6 +12,7 @@ use crate::{ use crate::{mount_child, prepare_to_move, MountKind, Mountable}; pub use dyn_child::*; pub use each::*; +pub use errors::*; pub use fragment::*; use leptos_reactive::Scope; #[cfg(all(target_arch = "wasm32", feature = "web"))] diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs new file mode 100644 index 000000000..2d10a1f76 --- /dev/null +++ b/leptos_dom/src/components/errors.rs @@ -0,0 +1,12 @@ +use crate::{IntoView, Transparent}; +use std::{error::Error, sync::Arc}; + +/// A struct to hold all the possible errors that could be provided by child Views +#[derive(Debug, Clone, Default)] +pub struct Errors(pub Vec>); + +impl IntoView for Errors { + fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { + Transparent::new(self).into_view(cx) + } +} From 6cbdc57f7af311dc415563b4e65702a32b38414e Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 15:49:42 -0800 Subject: [PATCH 02/13] Add working impl of ErrorBoundary --- leptos/src/error_boundary.rs | 2 -- leptos_dom/src/components/errors.rs | 46 +++++++++++++++++++++++++---- leptos_dom/src/hydration.rs | 2 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index f702b09be..9a9eedfdf 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -25,5 +25,3 @@ where false => fallback(cx, errors).into(), } } - -// impl IntoView for Result<(), Box> {} diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index 2d10a1f76..5dfc3ce58 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,12 +1,48 @@ -use crate::{IntoView, Transparent}; -use std::{error::Error, sync::Arc}; +use leptos_reactive::{use_context, RwSignal}; + +use crate::{HydrationCtx, HydrationKey, IntoView, Transparent}; +use std::{collections::HashMap, error::Error, rc::Rc}; /// A struct to hold all the possible errors that could be provided by child Views #[derive(Debug, Clone, Default)] -pub struct Errors(pub Vec>); +pub struct Errors(pub HashMap>); -impl IntoView for Errors { +impl IntoView for Result +where + T: IntoView + 'static, + E: std::error::Error + 'static, +{ fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { - Transparent::new(self).into_view(cx) + let errors = match use_context::>(cx) { + Some(e) => e, + None => { + #[cfg(debug_assertions)] + warn!( + "No ErrorBoundary components found! Returning errors will not be \ + handled and will silently disappear" + ); + return Transparent::new(()).into_view(cx); + } + }; + + match self { + Ok(stuff) => Transparent::new(stuff).into_view(cx), + Err(error) => { + errors.update(|errors: &mut Errors| { + errors.insert(HydrationCtx::id(), error) + }); + Transparent::new(()).into_view(cx) + } + } + } +} + +impl Errors { + /// Add an error to Errors that will be processed by `` + pub fn insert(&mut self, key: HydrationKey, error: E) + where + E: Error + 'static, + { + self.0.insert(key, Rc::new(error)); } } diff --git a/leptos_dom/src/hydration.rs b/leptos_dom/src/hydration.rs index 031f2e6c5..a9f21901c 100644 --- a/leptos_dom/src/hydration.rs +++ b/leptos_dom/src/hydration.rs @@ -49,7 +49,7 @@ cfg_if! { } /// A stable identifer within the server-rendering or hydration process. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct HydrationKey { /// The key of the previous component. pub previous: String, From db63eda2f515fe2f44dedf76962189fa2406d5d1 Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 16:02:06 -0800 Subject: [PATCH 03/13] Push gbj's updates --- integrations/axum/src/lib.rs | 4 --- leptos_dom/src/components/errors.rs | 40 ++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 6a5ff988a..63613dbdf 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -485,10 +485,6 @@ where } } -// impl IntoResponse for Pin>>>>{ -// todo!() -// } - /// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically /// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element /// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index 5dfc3ce58..e6db217b6 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,4 +1,4 @@ -use leptos_reactive::{use_context, RwSignal}; +use leptos_reactive::{on_cleanup, use_context, RwSignal}; use crate::{HydrationCtx, HydrationKey, IntoView, Transparent}; use std::{collections::HashMap, error::Error, rc::Rc}; @@ -10,7 +10,7 @@ pub struct Errors(pub HashMap>); impl IntoView for Result where T: IntoView + 'static, - E: std::error::Error + 'static, + E: std::error::Error + Send + Sync + 'static, { fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { let errors = match use_context::>(cx) { @@ -28,15 +28,36 @@ where match self { Ok(stuff) => Transparent::new(stuff).into_view(cx), Err(error) => { - errors.update(|errors: &mut Errors| { - errors.insert(HydrationCtx::id(), error) - }); + match use_context::>(cx) { + Some(errors) => { + let id = HydrationCtx::id(); + errors.update({ + let id = id.clone(); + move |errors: &mut Errors| errors.insert(id, error) + }); + + // remove the error from the list if this drops, + // i.e., if it's in a DynChild that switches from Err to Ok + // will this actually work? + on_cleanup(cx, move || { + errors.update(|errors: &mut Errors| { + errors.remove::(&id); + }); + }); + } + None => { + #[cfg(debug_assertions)] + warn!( + "No ErrorBoundary components found! Returning errors will not \ + be handled and will silently disappear" + ); + } + } Transparent::new(()).into_view(cx) } } } } - impl Errors { /// Add an error to Errors that will be processed by `` pub fn insert(&mut self, key: HydrationKey, error: E) @@ -45,4 +66,11 @@ impl Errors { { self.0.insert(key, Rc::new(error)); } + /// Remove an error to Errors that will be processed by `` + pub fn remove(&mut self, key: &HydrationKey) + where + E: Error + 'static, + { + self.0.remove(key); + } } From ad3ac5ad3cc845e528cbb492aeff56ca5f27462d Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 16:03:53 -0800 Subject: [PATCH 04/13] Remove silly thing --- examples/todo_app_sqlite_axum/src/todo.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 582a83869..5f7d16170 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -106,13 +106,11 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { } #[component] -pub fn ErrorBoundary(cx: Scope) -> impl IntoView { +pub fn ErrorComponent(cx: Scope) -> impl IntoView { provide_meta_context(cx); view! { cx, - - -

"ERROR"

+ Err(TodoAppError::AnError) } } From 452e397048c5b43d303b35c1296c75d6e5b37bef Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 16:47:22 -0800 Subject: [PATCH 05/13] Made todo_app_sqlite_axum throw an error --- examples/todo_app_sqlite_axum/README.md | 2 +- examples/todo_app_sqlite_axum/src/error.rs | 7 +++++++ examples/todo_app_sqlite_axum/src/todo.rs | 11 ++++++----- leptos/src/error_boundary.rs | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 examples/todo_app_sqlite_axum/src/error.rs diff --git a/examples/todo_app_sqlite_axum/README.md b/examples/todo_app_sqlite_axum/README.md index d618284f9..6f227552b 100644 --- a/examples/todo_app_sqlite_axum/README.md +++ b/examples/todo_app_sqlite_axum/README.md @@ -25,7 +25,7 @@ cargo leptos build --release ## Server Side Rendering without cargo-leptos To run it as a server side app with hydration, you'll need to have wasm-pack installed. -0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time +0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes. 1. Install wasm-pack ```bash cargo install wasm-pack diff --git a/examples/todo_app_sqlite_axum/src/error.rs b/examples/todo_app_sqlite_axum/src/error.rs new file mode 100644 index 000000000..f6082858a --- /dev/null +++ b/examples/todo_app_sqlite_axum/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TodoAppError { + #[error("An Error Occured")] + AnError, +} diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 5f7d16170..ad2f0b2b7 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -3,6 +3,7 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; use serde::{Deserialize, Serialize}; +use crate::error::TodoAppError; cfg_if! { if #[cfg(feature = "ssr")] { @@ -107,11 +108,8 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { #[component] pub fn ErrorComponent(cx: Scope) -> impl IntoView { - provide_meta_context(cx); - view! { - cx, - Err(TodoAppError::AnError) - } + // provide_meta_context(cx); + Err::(TodoAppError::AnError) } #[component] @@ -126,12 +124,14 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {

"My Tasks"

+ }/> +
} @@ -160,6 +160,7 @@ pub fn Todos(cx: Scope) -> impl IntoView { + "Loading..."

}> {move || { let existing_todos = { diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index 9a9eedfdf..e0f257999 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -11,7 +11,7 @@ pub fn ErrorBoundary( fallback: F, ) -> impl IntoView where - F: Fn(Scope, RwSignal) -> View + 'static, + F: Fn(Scope) -> View + 'static, { let errors: RwSignal = create_rw_signal(cx, Errors::default()); @@ -22,6 +22,6 @@ where move || match errors.get().0.is_empty() { true => children.clone(), - false => fallback(cx, errors).into(), + false => fallback(cx).into(), } } From ac343427e7ca8f38b3433cbade12bd1cbb6c533b Mon Sep 17 00:00:00 2001 From: benwis Date: Wed, 18 Jan 2023 23:21:08 -0800 Subject: [PATCH 06/13] Remember all the files --- examples/todo_app_sqlite_axum/Cargo.toml | 15 +++------------ examples/todo_app_sqlite_axum/src/file.rs | 4 ++-- examples/todo_app_sqlite_axum/src/lib.rs | 1 + examples/todo_app_sqlite_axum/src/todo.rs | 3 +++ 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/examples/todo_app_sqlite_axum/Cargo.toml b/examples/todo_app_sqlite_axum/Cargo.toml index a433b675a..c0d5dde68 100644 --- a/examples/todo_app_sqlite_axum/Cargo.toml +++ b/examples/todo_app_sqlite_axum/Cargo.toml @@ -33,22 +33,13 @@ sqlx = { version = "0.6.2", features = [ "runtime-tokio-rustls", "sqlite", ], optional = true } +thiserror = "1.0.38" [features] default = ["csr"] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] -ssr = [ - "dep:axum", - "dep:tower", - "dep:tower-http", - "dep:tokio", - "dep:sqlx", - "leptos/ssr", - "leptos_meta/ssr", - "leptos_router/ssr", - "leptos_axum", -] +ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "dep:sqlx", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "leptos_axum"] [package.metadata.cargo-all-features] denylist = [ @@ -103,4 +94,4 @@ lib-features = ["hydrate"] # If the --no-default-features flag should be used when compiling the lib target # # Optional. Defaults to false. -lib-default-features = false \ No newline at end of file +lib-default-features = false diff --git a/examples/todo_app_sqlite_axum/src/file.rs b/examples/todo_app_sqlite_axum/src/file.rs index 6db46933f..4db0e052c 100644 --- a/examples/todo_app_sqlite_axum/src/file.rs +++ b/examples/todo_app_sqlite_axum/src/file.rs @@ -13,7 +13,7 @@ if #[cfg(feature = "ssr")] { use tower_http::services::ServeDir; use std::sync::Arc; use leptos::{LeptosOptions, view}; - use crate::todo::{ErrorBoundary, ErrorBoundaryProps}; + use crate::todo::{ErrorComponent, ErrorComponentProps}; pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { let options = &*options; @@ -23,7 +23,7 @@ if #[cfg(feature = "ssr")] { if res.status() == StatusCode::OK { res.into_response() } else{ - let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| view! { cx, }); + let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| view! { cx, }); handler(req).await.into_response() } } diff --git a/examples/todo_app_sqlite_axum/src/lib.rs b/examples/todo_app_sqlite_axum/src/lib.rs index da641e645..55f334fca 100644 --- a/examples/todo_app_sqlite_axum/src/lib.rs +++ b/examples/todo_app_sqlite_axum/src/lib.rs @@ -1,5 +1,6 @@ use cfg_if::cfg_if; use leptos::*; +pub mod error; pub mod file; pub mod todo; diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index ad2f0b2b7..f8f06a3b1 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -160,7 +160,10 @@ pub fn Todos(cx: Scope) -> impl IntoView { + + + "Loading..."

}> {move || { let existing_todos = { From 64add54de64137a4752245946c2f1c99548e5a45 Mon Sep 17 00:00:00 2001 From: benwis Date: Thu, 19 Jan 2023 14:57:34 -0800 Subject: [PATCH 07/13] Add example error template and give the ability to access error info inside it --- .../src/error_template.rs | 28 +++++++++++++++++++ examples/todo_app_sqlite_axum/src/lib.rs | 1 + examples/todo_app_sqlite_axum/src/todo.rs | 7 ++--- leptos/src/error_boundary.rs | 4 +-- 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 examples/todo_app_sqlite_axum/src/error_template.rs diff --git a/examples/todo_app_sqlite_axum/src/error_template.rs b/examples/todo_app_sqlite_axum/src/error_template.rs new file mode 100644 index 000000000..23a022ef6 --- /dev/null +++ b/examples/todo_app_sqlite_axum/src/error_template.rs @@ -0,0 +1,28 @@ +use leptos::Errors; +use leptos::{view, For, ForProps, HydrationKey, IntoView, RwSignal, Scope, View}; +use std::error::Error; + +pub fn error_template(cx: Scope, errors: RwSignal) -> View { + println!("Errors: {:#?}", errors()); + view! {cx, +

"Errors"

+ "Error: " {error_string}

+ //

"Location: " {source}

+ } + } + /> + } + .into_view(cx) +} diff --git a/examples/todo_app_sqlite_axum/src/lib.rs b/examples/todo_app_sqlite_axum/src/lib.rs index 55f334fca..85f1fe54f 100644 --- a/examples/todo_app_sqlite_axum/src/lib.rs +++ b/examples/todo_app_sqlite_axum/src/lib.rs @@ -1,6 +1,7 @@ use cfg_if::cfg_if; use leptos::*; pub mod error; +pub mod error_template; pub mod file; pub mod todo; diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index f8f06a3b1..7f5611b3b 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -3,7 +3,7 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use crate::error::TodoAppError; +use crate::{error::TodoAppError, error_template::error_template}; cfg_if! { if #[cfg(feature = "ssr")] { @@ -124,7 +124,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {

"My Tasks"

- + impl IntoView { - - - "Loading..."

}> {move || { let existing_todos = { diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index e0f257999..9a9eedfdf 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -11,7 +11,7 @@ pub fn ErrorBoundary( fallback: F, ) -> impl IntoView where - F: Fn(Scope) -> View + 'static, + F: Fn(Scope, RwSignal) -> View + 'static, { let errors: RwSignal = create_rw_signal(cx, Errors::default()); @@ -22,6 +22,6 @@ where move || match errors.get().0.is_empty() { true => children.clone(), - false => fallback(cx).into(), + false => fallback(cx, errors).into(), } } From 3d769c9f21f2e3dccdd15ec6ac2d1095a23a62f3 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 20 Jan 2023 13:14:09 -0500 Subject: [PATCH 08/13] Clean up some rendering issues and the panic when cleaning up --- leptos_dom/src/components/errors.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index e6db217b6..e79048a98 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,6 +1,6 @@ -use leptos_reactive::{on_cleanup, use_context, RwSignal}; +use leptos_reactive::{on_cleanup, queue_microtask, use_context, RwSignal}; -use crate::{HydrationCtx, HydrationKey, IntoView, Transparent}; +use crate::{HydrationCtx, HydrationKey, IntoView}; use std::{collections::HashMap, error::Error, rc::Rc}; /// A struct to hold all the possible errors that could be provided by child Views @@ -13,20 +13,8 @@ where E: std::error::Error + Send + Sync + 'static, { fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { - let errors = match use_context::>(cx) { - Some(e) => e, - None => { - #[cfg(debug_assertions)] - warn!( - "No ErrorBoundary components found! Returning errors will not be \ - handled and will silently disappear" - ); - return Transparent::new(()).into_view(cx); - } - }; - match self { - Ok(stuff) => Transparent::new(stuff).into_view(cx), + Ok(stuff) => stuff.into_view(cx), Err(error) => { match use_context::>(cx) { Some(errors) => { @@ -40,8 +28,10 @@ where // i.e., if it's in a DynChild that switches from Err to Ok // will this actually work? on_cleanup(cx, move || { - errors.update(|errors: &mut Errors| { - errors.remove::(&id); + queue_microtask(move || { + errors.update(|errors: &mut Errors| { + errors.remove::(&id); + }); }); }); } @@ -53,7 +43,7 @@ where ); } } - Transparent::new(()).into_view(cx) + ().into_view(cx) } } } From b5ab7b107ad90f85cfcece852e437017e342693e Mon Sep 17 00:00:00 2001 From: benwis Date: Fri, 20 Jan 2023 15:52:43 -0800 Subject: [PATCH 09/13] Test of SSR/Hydration of ErrorBoundary --- .../src/fallback.rs} | 0 examples/hackernews_axum/src/lib.rs | 2 +- examples/hackernews_axum/src/main.rs | 2 +- .../src/error_template.rs | 16 +++++++-------- .../src/fallback.rs} | 20 ++++++++++++------- examples/todo_app_sqlite_axum/src/lib.rs | 2 +- examples/todo_app_sqlite_axum/src/main.rs | 2 +- examples/todo_app_sqlite_axum/src/todo.rs | 15 +++++++------- leptos/src/error_boundary.rs | 4 ++-- 9 files changed, 34 insertions(+), 29 deletions(-) rename examples/{todo_app_sqlite_axum/src/file.rs => hackernews_axum/src/fallback.rs} (100%) rename examples/{hackernews_axum/src/file.rs => todo_app_sqlite_axum/src/fallback.rs} (62%) diff --git a/examples/todo_app_sqlite_axum/src/file.rs b/examples/hackernews_axum/src/fallback.rs similarity index 100% rename from examples/todo_app_sqlite_axum/src/file.rs rename to examples/hackernews_axum/src/fallback.rs diff --git a/examples/hackernews_axum/src/lib.rs b/examples/hackernews_axum/src/lib.rs index e1ca4aba2..4183f4722 100644 --- a/examples/hackernews_axum/src/lib.rs +++ b/examples/hackernews_axum/src/lib.rs @@ -3,7 +3,7 @@ use leptos::{component, view, IntoView, Scope}; use leptos_meta::*; use leptos_router::*; mod api; -pub mod file; +pub mod fallback; pub mod handlers; mod routes; use routes::nav::*; diff --git a/examples/hackernews_axum/src/main.rs b/examples/hackernews_axum/src/main.rs index 2c568c445..dd37230c2 100644 --- a/examples/hackernews_axum/src/main.rs +++ b/examples/hackernews_axum/src/main.rs @@ -11,7 +11,7 @@ if #[cfg(feature = "ssr")] { }; use leptos_axum::{generate_route_list, LeptosRoutes}; use std::sync::Arc; - use hackernews_axum::file::file_handler; + use hackernews_axum::fallback::file_and_error_handler; #[tokio::main] async fn main() { diff --git a/examples/todo_app_sqlite_axum/src/error_template.rs b/examples/todo_app_sqlite_axum/src/error_template.rs index 23a022ef6..593c39b43 100644 --- a/examples/todo_app_sqlite_axum/src/error_template.rs +++ b/examples/todo_app_sqlite_axum/src/error_template.rs @@ -1,9 +1,12 @@ use leptos::Errors; -use leptos::{view, For, ForProps, HydrationKey, IntoView, RwSignal, Scope, View}; -use std::error::Error; +use leptos::{view, For, ForProps, IntoView, RwSignal, Scope, View}; -pub fn error_template(cx: Scope, errors: RwSignal) -> View { - println!("Errors: {:#?}", errors()); +// A basic function to display errors served by the error boundaries. Feel free to do more complicated things +// here than just displaying them +pub fn error_template(cx: Scope, errors: Option>) -> View { + let Some(errors) = errors else { + panic!("No Errors found and we expected errors!"); + }; view! {cx,

"Errors"

) -> View { key=|error| error.0.clone() // renders each item to a view view= move |error| { - println!("SOURCE: {:#?}", error.1.source()); - // let source: String = error.1.source().unwrap().to_string(); - let error_string = error.1.to_string(); + let error_string = error.1.to_string(); view! { cx,

"Error: " {error_string}

- //

"Location: " {source}

} } /> diff --git a/examples/hackernews_axum/src/file.rs b/examples/todo_app_sqlite_axum/src/fallback.rs similarity index 62% rename from examples/hackernews_axum/src/file.rs rename to examples/todo_app_sqlite_axum/src/fallback.rs index 5a2439890..1b29937ad 100644 --- a/examples/hackernews_axum/src/file.rs +++ b/examples/todo_app_sqlite_axum/src/fallback.rs @@ -5,22 +5,26 @@ if #[cfg(feature = "ssr")] { use axum::{ body::{boxed, Body, BoxBody}, extract::Extension, + response::IntoResponse, http::{Request, Response, StatusCode, Uri}, }; + use axum::response::Response as AxumResponse; use tower::ServiceExt; use tower_http::services::ServeDir; use std::sync::Arc; - use leptos::LeptosOptions; - - pub async fn file_handler(uri: Uri, Extension(options): Extension>) -> Result, (StatusCode, String)> { + use leptos::{LeptosOptions}; + use crate::error_template::error_template; + pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { let options = &*options; let root = options.site_root.clone(); - let res = get_static_file(uri.clone(), &root).await?; + let res = get_static_file(uri.clone(), &root).await.unwrap(); - match res.status() { - StatusCode::OK => Ok(res), - _ => Err((res.status(), "File Not Found".to_string())) + if res.status() == StatusCode::OK { + res.into_response() + } else{ + let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None)); + handler(req).await.into_response() } } @@ -37,5 +41,7 @@ if #[cfg(feature = "ssr")] { )), } } + + } } diff --git a/examples/todo_app_sqlite_axum/src/lib.rs b/examples/todo_app_sqlite_axum/src/lib.rs index 85f1fe54f..06ff489c0 100644 --- a/examples/todo_app_sqlite_axum/src/lib.rs +++ b/examples/todo_app_sqlite_axum/src/lib.rs @@ -2,7 +2,7 @@ use cfg_if::cfg_if; use leptos::*; pub mod error; pub mod error_template; -pub mod file; +pub mod fallback; pub mod todo; // Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong. diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index d7bfec628..615c0416c 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -10,7 +10,7 @@ if #[cfg(feature = "ssr")] { }; use crate::todo::*; use todo_app_sqlite_axum::*; - use crate::file::file_and_error_handler; + use crate::fallback::file_and_error_handler; use leptos_axum::{generate_route_list, LeptosRoutes}; use std::sync::Arc; diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 7f5611b3b..2f0ea1ab5 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -3,7 +3,8 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use crate::{error::TodoAppError, error_template::error_template}; +use crate:: error_template::error_template; +use crate::error::TodoAppError; cfg_if! { if #[cfg(feature = "ssr")] { @@ -107,11 +108,9 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { } #[component] -pub fn ErrorComponent(cx: Scope) -> impl IntoView { - // provide_meta_context(cx); +pub fn Error(cx: Scope) -> impl IntoView{ Err::(TodoAppError::AnError) } - #[component] pub fn TodoApp(cx: Scope) -> impl IntoView { provide_meta_context(cx); @@ -124,14 +123,14 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {

"My Tasks"

- + + + }/> -
} @@ -160,7 +159,7 @@ pub fn Todos(cx: Scope) -> impl IntoView { - + "Loading..."

}> {move || { let existing_todos = { diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index 9a9eedfdf..879e3ba31 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -11,7 +11,7 @@ pub fn ErrorBoundary( fallback: F, ) -> impl IntoView where - F: Fn(Scope, RwSignal) -> View + 'static, + F: Fn(Scope, Option>) -> View + 'static, { let errors: RwSignal = create_rw_signal(cx, Errors::default()); @@ -22,6 +22,6 @@ where move || match errors.get().0.is_empty() { true => children.clone(), - false => fallback(cx, errors).into(), + false => fallback(cx, Some(errors)).into(), } } From 2d88524354158f11b2354f709ec8f6d86ac8371b Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 21 Jan 2023 10:56:38 -0800 Subject: [PATCH 10/13] Wrap cfg_if to prevent on_cleanup from panicing on the server --- leptos_dom/src/components/errors.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index e79048a98..cf1996b61 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,6 +1,6 @@ -use leptos_reactive::{on_cleanup, queue_microtask, use_context, RwSignal}; - use crate::{HydrationCtx, HydrationKey, IntoView}; +use cfg_if::cfg_if; +use leptos_reactive::{on_cleanup, queue_microtask, use_context, RwSignal}; use std::{collections::HashMap, error::Error, rc::Rc}; /// A struct to hold all the possible errors that could be provided by child Views @@ -26,14 +26,18 @@ where // remove the error from the list if this drops, // i.e., if it's in a DynChild that switches from Err to Ok - // will this actually work? - on_cleanup(cx, move || { - queue_microtask(move || { - errors.update(|errors: &mut Errors| { - errors.remove::(&id); + // Only can run on the client, will panic on the server + cfg_if! { + if #[cfg(feature = "hydrate")] { + on_cleanup(cx, move || { + queue_microtask(move || { + errors.update(|errors: &mut Errors| { + errors.remove::(&id); + }); + }); }); - }); - }); + } + } } None => { #[cfg(debug_assertions)] From 3e04318082a78874be5ebe6542617c016d6c62f4 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 21 Jan 2023 10:58:47 -0800 Subject: [PATCH 11/13] Remove extra --- examples/todo_app_sqlite_axum/Cargo.toml | 1 - examples/todo_app_sqlite_axum/src/todo.rs | 10 +--------- leptos_dom/src/components/errors.rs | 19 ++++++++++--------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/examples/todo_app_sqlite_axum/Cargo.toml b/examples/todo_app_sqlite_axum/Cargo.toml index c0d5dde68..01e633646 100644 --- a/examples/todo_app_sqlite_axum/Cargo.toml +++ b/examples/todo_app_sqlite_axum/Cargo.toml @@ -33,7 +33,6 @@ sqlx = { version = "0.6.2", features = [ "runtime-tokio-rustls", "sqlite", ], optional = true } -thiserror = "1.0.38" [features] default = ["csr"] diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 2f0ea1ab5..72c42d919 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -3,8 +3,6 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; use serde::{Deserialize, Serialize}; -use crate:: error_template::error_template; -use crate::error::TodoAppError; cfg_if! { if #[cfg(feature = "ssr")] { @@ -107,10 +105,6 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { .map_err(|e| ServerFnError::ServerError(e.to_string())) } -#[component] -pub fn Error(cx: Scope) -> impl IntoView{ - Err::(TodoAppError::AnError) -} #[component] pub fn TodoApp(cx: Scope) -> impl IntoView { provide_meta_context(cx); @@ -126,10 +120,9 @@ pub fn TodoApp(cx: Scope) -> impl IntoView { -
}/> +
@@ -159,7 +152,6 @@ pub fn Todos(cx: Scope) -> impl IntoView { - "Loading..."

}> {move || { let existing_todos = { diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index cf1996b61..95a191ed3 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,6 +1,6 @@ use crate::{HydrationCtx, HydrationKey, IntoView}; use cfg_if::cfg_if; -use leptos_reactive::{on_cleanup, queue_microtask, use_context, RwSignal}; +use leptos_reactive::{use_context, RwSignal}; use std::{collections::HashMap, error::Error, rc::Rc}; /// A struct to hold all the possible errors that could be provided by child Views @@ -28,16 +28,17 @@ where // i.e., if it's in a DynChild that switches from Err to Ok // Only can run on the client, will panic on the server cfg_if! { - if #[cfg(feature = "hydrate")] { - on_cleanup(cx, move || { - queue_microtask(move || { - errors.update(|errors: &mut Errors| { - errors.remove::(&id); - }); - }); + if #[cfg(any(feature = "hydrate", feature="csr"))] { + use leptos_reactive::{on_cleanup, queue_microtask}; + on_cleanup(cx, move || { + queue_microtask(move || { + errors.update(|errors: &mut Errors| { + errors.remove::(&id); }); + }); + }); + } } - } } None => { #[cfg(debug_assertions)] From 23bd399239adb02be71dda7d359f561f48d95a73 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 21 Jan 2023 11:25:36 -0800 Subject: [PATCH 12/13] I did, I did break it --- examples/todo_app_sqlite_axum/src/error.rs | 7 ------- examples/todo_app_sqlite_axum/src/lib.rs | 1 - 2 files changed, 8 deletions(-) delete mode 100644 examples/todo_app_sqlite_axum/src/error.rs diff --git a/examples/todo_app_sqlite_axum/src/error.rs b/examples/todo_app_sqlite_axum/src/error.rs deleted file mode 100644 index f6082858a..000000000 --- a/examples/todo_app_sqlite_axum/src/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TodoAppError { - #[error("An Error Occured")] - AnError, -} diff --git a/examples/todo_app_sqlite_axum/src/lib.rs b/examples/todo_app_sqlite_axum/src/lib.rs index 06ff489c0..041dcb51b 100644 --- a/examples/todo_app_sqlite_axum/src/lib.rs +++ b/examples/todo_app_sqlite_axum/src/lib.rs @@ -1,6 +1,5 @@ use cfg_if::cfg_if; use leptos::*; -pub mod error; pub mod error_template; pub mod fallback; pub mod todo; From 81ab77e8ea7ae03a617a760a74f509020a266d78 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 21 Jan 2023 11:54:55 -0800 Subject: [PATCH 13/13] One more time! --- .../hackernews_axum/src/error_template.rs | 28 +++++++++++++++++++ examples/hackernews_axum/src/fallback.rs | 6 ++-- examples/hackernews_axum/src/lib.rs | 1 + examples/hackernews_axum/src/main.rs | 4 +-- 4 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 examples/hackernews_axum/src/error_template.rs diff --git a/examples/hackernews_axum/src/error_template.rs b/examples/hackernews_axum/src/error_template.rs new file mode 100644 index 000000000..593c39b43 --- /dev/null +++ b/examples/hackernews_axum/src/error_template.rs @@ -0,0 +1,28 @@ +use leptos::Errors; +use leptos::{view, For, ForProps, IntoView, RwSignal, Scope, View}; + +// A basic function to display errors served by the error boundaries. Feel free to do more complicated things +// here than just displaying them +pub fn error_template(cx: Scope, errors: Option>) -> View { + let Some(errors) = errors else { + panic!("No Errors found and we expected errors!"); + }; + view! {cx, +

"Errors"

+ "Error: " {error_string}

+ } + } + /> + } + .into_view(cx) +} diff --git a/examples/hackernews_axum/src/fallback.rs b/examples/hackernews_axum/src/fallback.rs index 4db0e052c..1b29937ad 100644 --- a/examples/hackernews_axum/src/fallback.rs +++ b/examples/hackernews_axum/src/fallback.rs @@ -12,8 +12,8 @@ if #[cfg(feature = "ssr")] { use tower::ServiceExt; use tower_http::services::ServeDir; use std::sync::Arc; - use leptos::{LeptosOptions, view}; - use crate::todo::{ErrorComponent, ErrorComponentProps}; + use leptos::{LeptosOptions}; + use crate::error_template::error_template; pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension>, req: Request) -> AxumResponse { let options = &*options; @@ -23,7 +23,7 @@ if #[cfg(feature = "ssr")] { if res.status() == StatusCode::OK { res.into_response() } else{ - let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| view! { cx, }); + let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None)); handler(req).await.into_response() } } diff --git a/examples/hackernews_axum/src/lib.rs b/examples/hackernews_axum/src/lib.rs index 4183f4722..d6e612fb0 100644 --- a/examples/hackernews_axum/src/lib.rs +++ b/examples/hackernews_axum/src/lib.rs @@ -3,6 +3,7 @@ use leptos::{component, view, IntoView, Scope}; use leptos_meta::*; use leptos_router::*; mod api; +pub mod error_template; pub mod fallback; pub mod handlers; mod routes; diff --git a/examples/hackernews_axum/src/main.rs b/examples/hackernews_axum/src/main.rs index dd37230c2..fa2f06a36 100644 --- a/examples/hackernews_axum/src/main.rs +++ b/examples/hackernews_axum/src/main.rs @@ -26,9 +26,9 @@ if #[cfg(feature = "ssr")] { // build our application with a route let app = Router::new() - .route("/favicon.ico", get(file_handler)) + .route("/favicon.ico", get(file_and_error_handler)) .leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, } ) - .fallback(file_handler) + .fallback(file_and_error_handler) .layer(Extension(Arc::new(leptos_options))); // run our app with hyper