diff --git a/src/app.rs b/src/app.rs index 6df1766..dd0e0d2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,7 @@ use leptos_meta::*; use leptos_router::*; use crate::pages::login::*; use crate::pages::signup::*; +use crate::error_template::{AppError, ErrorTemplate}; #[component] pub fn App() -> impl IntoView { @@ -21,11 +22,17 @@ pub fn App() -> impl IntoView { // content for this welcome page - <Router> + <Router fallback=|| { + let mut outside_errors = Errors::default(); + outside_errors.insert_with_default_key(AppError::NotFound); + view! { + <ErrorTemplate outside_errors/> + } + .into_view() + }> <main> <Routes> <Route path="" view=HomePage/> - <Route path="/*any" view=NotFound/> <Route path="/login" view=Login /> <Route path="/signup" view=Signup /> </Routes> diff --git a/src/error_template.rs b/src/error_template.rs new file mode 100644 index 0000000..b30718c --- /dev/null +++ b/src/error_template.rs @@ -0,0 +1,73 @@ +use http::status::StatusCode; +use leptos::*; +use thiserror::Error; + +#[cfg(feature = "ssr")] +use leptos_axum::ResponseOptions; + +#[derive(Clone, Debug, Error)] +pub enum AppError { + #[error("Not Found")] + NotFound, +} + +impl AppError { + pub fn status_code(&self) -> StatusCode { + match self { + AppError::NotFound => StatusCode::NOT_FOUND, + } + } +} + +// A basic function to display errors served by the error boundaries. +// Feel free to do more complicated things here than just displaying the error. +#[component] +pub fn ErrorTemplate( + #[prop(optional)] outside_errors: Option<Errors>, + #[prop(optional)] errors: Option<RwSignal<Errors>>, +) -> impl IntoView { + let errors = match outside_errors { + Some(e) => create_rw_signal(e), + None => match errors { + Some(e) => e, + None => panic!("No Errors found and we expected errors!"), + }, + }; + // Get Errors from Signal + let errors = errors.get_untracked(); + + // Downcast lets us take a type that implements `std::error::Error` + let errors: Vec<AppError> = errors + .into_iter() + .filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned()) + .collect(); + + // Only the response code for the first error is actually sent from the server + // this may be customized by the specific application + #[cfg(feature = "ssr")] + { + let response = use_context::<ResponseOptions>(); + if let Some(response) = response { + response.set_status(errors[0].status_code()); + } + } + + view! { + <h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1> + <For + // a function that returns the items we're iterating over; a signal is fine + each= move || {errors.clone().into_iter().enumerate()} + // a unique key for each item as a reference + key=|(index, _error)| *index + // renders each item to a view + children=move |error| { + let error_string = error.1.to_string(); + let error_code= error.1.status_code(); + view! { + <h2>{error_code.to_string()}</h2> + <p>"Error: " {error_string}</p> + } + } + /> + } +} diff --git a/src/lib.rs b/src/lib.rs index 28a9044..be30f52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod pages; pub mod users; pub mod search; pub mod fileserv; +pub mod error_template; use cfg_if::cfg_if; cfg_if! {