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 {