Create example of file_and_error handler for Axum, and create <ErrorBoundary/>

for leptos
This commit is contained in:
benwis 2023-01-18 12:59:15 -08:00
parent 300cc4f54c
commit 3215e44c9a
8 changed files with 75 additions and 10 deletions

View file

@ -5,22 +5,26 @@ if #[cfg(feature = "ssr")] {
use axum::{ use axum::{
body::{boxed, Body, BoxBody}, body::{boxed, Body, BoxBody},
extract::Extension, extract::Extension,
response::IntoResponse,
http::{Request, Response, StatusCode, Uri}, http::{Request, Response, StatusCode, Uri},
}; };
use axum::response::Response as AxumResponse;
use tower::ServiceExt; use tower::ServiceExt;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use std::sync::Arc; use std::sync::Arc;
use leptos::LeptosOptions; use leptos::{LeptosOptions, view};
use crate::todo::{ErrorBoundary, ErrorBoundaryProps};
pub async fn file_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>) -> Result<Response<BoxBody>, (StatusCode, String)> {
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
let options = &*options; let options = &*options;
let root = options.site_root.clone(); 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() { if res.status() == StatusCode::OK {
StatusCode::OK => Ok(res), res.into_response()
_ => Err((res.status(), "File Not Found".to_string())) } else{
let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| view! { cx, <ErrorBoundary/> });
handler(req).await.into_response()
} }
} }
@ -37,5 +41,7 @@ if #[cfg(feature = "ssr")] {
)), )),
} }
} }
} }
} }

View file

@ -10,7 +10,7 @@ if #[cfg(feature = "ssr")] {
}; };
use crate::todo::*; use crate::todo::*;
use todo_app_sqlite_axum::*; 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 leptos_axum::{generate_route_list, LeptosRoutes};
use std::sync::Arc; use std::sync::Arc;
@ -36,7 +36,7 @@ if #[cfg(feature = "ssr")] {
let app = Router::new() let app = Router::new()
.route("/api/*fn_name", post(leptos_axum::handle_server_fns)) .route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <TodoApp/> } ) .leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <TodoApp/> } )
.fallback(file_handler) .fallback(file_and_error_handler)
.layer(Extension(Arc::new(leptos_options))); .layer(Extension(Arc::new(leptos_options)));
// run our app with hyper // run our app with hyper

View file

@ -105,6 +105,17 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
.map_err(|e| ServerFnError::ServerError(e.to_string())) .map_err(|e| ServerFnError::ServerError(e.to_string()))
} }
#[component]
pub fn ErrorBoundary(cx: Scope) -> impl IntoView {
provide_meta_context(cx);
view! {
cx,
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite_axum.css"/>
<h1>"ERROR"</h1>
}
}
#[component] #[component]
pub fn TodoApp(cx: Scope) -> impl IntoView { pub fn TodoApp(cx: Scope) -> impl IntoView {
provide_meta_context(cx); provide_meta_context(cx);

View file

@ -485,6 +485,10 @@ where
} }
} }
// impl IntoResponse for Pin<Box<dyn Future<Output = Response<StreamBody<PinnedHtmlStream>>>>>{
// todo!()
// }
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically /// 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 /// 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. /// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.

View file

@ -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<F>(
cx: Scope,
/// The components inside the tag which will get rendered
children: Box<dyn FnOnce(Scope) -> Fragment>,
/// A fallback that will be shown if an error occurs.
fallback: F,
) -> impl IntoView
where
F: Fn(Scope, RwSignal<Errors>) -> View + 'static,
{
let errors: RwSignal<Errors> = 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<dyn Error>> {}

View file

@ -138,7 +138,8 @@ pub use leptos_server::*;
pub use tracing; pub use tracing;
pub use typed_builder; pub use typed_builder;
mod error_boundary;
pub use error_boundary::*;
mod for_loop; mod for_loop;
pub use for_loop::*; pub use for_loop::*;
mod suspense; mod suspense;

View file

@ -1,5 +1,6 @@
mod dyn_child; mod dyn_child;
mod each; mod each;
mod errors;
mod fragment; mod fragment;
mod unit; mod unit;
@ -11,6 +12,7 @@ use crate::{
use crate::{mount_child, prepare_to_move, MountKind, Mountable}; use crate::{mount_child, prepare_to_move, MountKind, Mountable};
pub use dyn_child::*; pub use dyn_child::*;
pub use each::*; pub use each::*;
pub use errors::*;
pub use fragment::*; pub use fragment::*;
use leptos_reactive::Scope; use leptos_reactive::Scope;
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]

View file

@ -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<Arc<dyn Error>>);
impl IntoView for Errors {
fn into_view(self, cx: leptos_reactive::Scope) -> crate::View {
Transparent::new(self).into_view(cx)
}
}