mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-14 00:27:12 +00:00
Several Minor Updates on Examples (#427)
This commit is contained in:
parent
1f6a326268
commit
63a7a4dec1
10 changed files with 712 additions and 774 deletions
|
@ -1,9 +1,8 @@
|
||||||
# Leptos Todo App Sqlite with Axum
|
# Leptos Errors Demonstration with Axum
|
||||||
|
This example demonstrates how Leptos Errors can work with an Axum backend on a server.
|
||||||
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
|
|
||||||
|
|
||||||
## Client Side Rendering
|
## Client Side Rendering
|
||||||
This example cannot be built as a trunk standalone CSR-only app as it requires the server to send HTTP Status Codes.
|
This example cannot be built as a trunk standalone CSR-only app as it requires the server to send status codes.
|
||||||
|
|
||||||
## Server Side Rendering with cargo-leptos
|
## Server Side Rendering with cargo-leptos
|
||||||
cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos)
|
cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos)
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::errors::AppError;
|
use crate::errors::AppError;
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use leptos::Errors;
|
use leptos::Errors;
|
||||||
use leptos::{
|
use leptos::*;
|
||||||
component, create_rw_signal, use_context, view, For, ForProps, IntoView, RwSignal, Scope,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use leptos_axum::ResponseOptions;
|
use leptos_axum::ResponseOptions;
|
||||||
|
|
||||||
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
|
// A basic function to display errors served by the error boundaries.
|
||||||
// here than just displaying them
|
// Feel free to do more complicated things here than just displaying them.
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ErrorTemplate(
|
pub fn ErrorTemplate(
|
||||||
cx: Scope,
|
cx: Scope,
|
||||||
|
@ -35,32 +34,29 @@ pub fn ErrorTemplate(
|
||||||
|
|
||||||
// Only the response code for the first error is actually sent from the server
|
// Only the response code for the first error is actually sent from the server
|
||||||
// this may be customized by the specific application
|
// this may be customized by the specific application
|
||||||
cfg_if! {
|
cfg_if! { if #[cfg(feature="ssr")] {
|
||||||
if #[cfg(feature="ssr")]{
|
|
||||||
let response = use_context::<ResponseOptions>(cx);
|
let response = use_context::<ResponseOptions>(cx);
|
||||||
if let Some(response) = response{
|
if let Some(response) = response {
|
||||||
response.set_status(errors[0].status_code());
|
response.set_status(errors[0].status_code());
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
view! {cx,
|
view! { cx,
|
||||||
<h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1>
|
<h1>{if errors.len() > 1 {"Errors"} else {"Error"}}</h1>
|
||||||
<For
|
<For
|
||||||
// a function that returns the items we're iterating over; a signal is fine
|
// a function that returns the items we're iterating over; a signal is fine
|
||||||
each= move || {errors.clone().into_iter().enumerate()}
|
each= move || {errors.clone().into_iter().enumerate()}
|
||||||
// a unique key for each item as a reference
|
// a unique key for each item as a reference
|
||||||
key=|(index, _error)| *index
|
key=|(index, _error)| *index
|
||||||
// renders each item to a view
|
// renders each item to a view
|
||||||
view= move |error| {
|
view= move |error| {
|
||||||
let error_string = error.1.to_string();
|
let error_string = error.1.to_string();
|
||||||
let error_code= error.1.status_code();
|
let error_code= error.1.status_code();
|
||||||
view! {
|
view! { cx,
|
||||||
cx,
|
<h2>{error_code.to_string()}</h2>
|
||||||
<h2>{error_code.to_string()}</h2>
|
<p>"Error: " {error_string}</p>
|
||||||
<p>"Error: " {error_string}</p>
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||||
if #[cfg(feature = "ssr")] {
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{boxed, Body, BoxBody},
|
body::{boxed, Body, BoxBody},
|
||||||
extract::Extension,
|
extract::Extension,
|
||||||
|
@ -43,7 +42,4 @@ if #[cfg(feature = "ssr")] {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,17 +2,14 @@ use crate::{
|
||||||
error_template::{ErrorTemplate, ErrorTemplateProps},
|
error_template::{ErrorTemplate, ErrorTemplateProps},
|
||||||
errors::AppError,
|
errors::AppError,
|
||||||
};
|
};
|
||||||
use cfg_if::cfg_if;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
#[cfg(feature = "ssr")]
|
||||||
pub fn register_server_functions() {
|
pub fn register_server_functions() {
|
||||||
_ = CauseInternalServerError::register();
|
_ = CauseInternalServerError::register();
|
||||||
_ = CauseNotFoundError::register();
|
}
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
#[server(CauseInternalServerError, "/api")]
|
#[server(CauseInternalServerError, "/api")]
|
||||||
pub async fn cause_internal_server_error() -> Result<(), ServerFnError> {
|
pub async fn cause_internal_server_error() -> Result<(), ServerFnError> {
|
||||||
|
@ -24,11 +21,6 @@ pub async fn cause_internal_server_error() -> Result<(), ServerFnError> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(CauseNotFoundError, "/api")]
|
|
||||||
pub async fn cause_not_found_error() -> Result<(), ServerFnError> {
|
|
||||||
Err(ServerFnError::ServerError("Not Found".to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App(cx: Scope) -> impl IntoView {
|
pub fn App(cx: Scope) -> impl IntoView {
|
||||||
//let id = use_context::<String>(cx);
|
//let id = use_context::<String>(cx);
|
||||||
|
@ -45,9 +37,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="" view=|cx| view! {
|
<Route path="" view=|cx| view! {
|
||||||
cx,
|
cx,
|
||||||
<ErrorBoundary fallback=|cx, errors| view!{cx, <ErrorTemplate errors=errors/>}>
|
<ExampleErrors/>
|
||||||
<ExampleErrors/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
}/>
|
}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
@ -57,20 +47,29 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ExampleErrors(cx: Scope) -> impl IntoView {
|
pub fn ExampleErrors(cx: Scope) -> impl IntoView {
|
||||||
view! {
|
let generate_internal_error = create_server_action::<CauseInternalServerError>(cx);
|
||||||
cx,
|
|
||||||
<p>
|
view! { cx,
|
||||||
"This link will load a 404 page since it does not exist. Verify with browser development tools:"
|
<p>
|
||||||
<a href="/404">"This Page Does not Exist"</a>
|
"These links will load 404 pages since they do not exist. Verify with browser development tools: " <br/>
|
||||||
</p>
|
<a href="/404">"This links to a page that does not exist"</a><br/>
|
||||||
<p>
|
<a href="/404" target="_blank">"Same link, but in a new tab"</a>
|
||||||
"The following <div> will always contain an error and cause the page to produce status 500. Check browser dev tools. "
|
</p>
|
||||||
</p>
|
<p>
|
||||||
<div>
|
"After pressing this button check browser network tools. Can be used even when WASM is blocked:"
|
||||||
<ErrorBoundary fallback=|cx, errors| view!{cx, <ErrorTemplate errors=errors/>}>
|
<ActionForm action=generate_internal_error>
|
||||||
<ReturnsError/>
|
<input name="error1" type="submit" value="Generate Internal Server Error"/>
|
||||||
</ErrorBoundary>
|
</ActionForm>
|
||||||
</div>
|
</p>
|
||||||
|
<p>"The following <div> will always contain an error and cause this page to produce status 500. Check browser dev tools. "</p>
|
||||||
|
<div>
|
||||||
|
// note that the error boundries could be placed above in the Router or lower down
|
||||||
|
// in a particular route. The generated errors on the entire page contribue to the
|
||||||
|
// final status code sent by the server when producing ssr pages.
|
||||||
|
<ErrorBoundary fallback=|cx, errors| view!{cx, <ErrorTemplate errors=errors/>}>
|
||||||
|
<ReturnsError/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,74 +1,72 @@
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use leptos::*;
|
|
||||||
// boilerplate to run in different modes
|
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||||
cfg_if! {
|
use crate::fallback::file_and_error_handler;
|
||||||
if #[cfg(feature = "ssr")] {
|
use crate::landing::*;
|
||||||
|
use axum::body::Body as AxumBody;
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{post, get},
|
|
||||||
extract::{Extension, Path},
|
extract::{Extension, Path},
|
||||||
http::Request,
|
http::Request,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use axum::body::Body as AxumBody;
|
|
||||||
use crate::landing::*;
|
|
||||||
use errors_axum::*;
|
use errors_axum::*;
|
||||||
use crate::fallback::file_and_error_handler;
|
use leptos::*;
|
||||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
}}
|
||||||
|
|
||||||
//Define a handler to test extractor with state
|
//Define a handler to test extractor with state
|
||||||
async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<AxumBody>) -> Response{
|
#[cfg(feature = "ssr")]
|
||||||
let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(),
|
async fn custom_handler(
|
||||||
move |cx| {
|
Path(id): Path<String>,
|
||||||
provide_context(cx, id.clone());
|
Extension(options): Extension<Arc<LeptosOptions>>,
|
||||||
},
|
req: Request<AxumBody>,
|
||||||
|cx| view! { cx, <App/> }
|
) -> Response {
|
||||||
);
|
let handler = leptos_axum::render_app_to_stream_with_context(
|
||||||
handler(req).await.into_response()
|
(*options).clone(),
|
||||||
}
|
move |cx| {
|
||||||
|
provide_context(cx, id.clone());
|
||||||
|
},
|
||||||
|
|cx| view! { cx, <App/> },
|
||||||
|
);
|
||||||
|
handler(req).await.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[cfg(feature = "ssr")]
|
||||||
async fn main() {
|
#[tokio::main]
|
||||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
async fn main() {
|
||||||
|
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||||
|
|
||||||
crate::landing::register_server_functions();
|
crate::landing::register_server_functions();
|
||||||
|
|
||||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||||
let conf = get_configuration(None).await.unwrap();
|
let conf = get_configuration(None).await.unwrap();
|
||||||
let leptos_options = conf.leptos_options;
|
let leptos_options = conf.leptos_options;
|
||||||
let addr = leptos_options.site_address;
|
let addr = leptos_options.site_address;
|
||||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
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))
|
||||||
.route("/special/:id", get(custom_handler))
|
.route("/special/:id", get(custom_handler))
|
||||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> } )
|
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> })
|
||||||
.fallback(file_and_error_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
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
log!("listening on http://{}", &addr);
|
log!("listening on http://{}", &addr);
|
||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// client-only stuff for Trunk
|
// this is if we were using client-only rending with Trunk
|
||||||
else {
|
#[cfg(not(feature = "ssr"))]
|
||||||
use todo_app_sqlite_axum::landing::*;
|
pub fn main() {
|
||||||
|
// This example cannot be built as a trunk standalone CSR-only app.
|
||||||
pub fn main() {
|
// The server is needed to demonstrate the error statuses.
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
_ = console_log::init_with_level(log::Level::Debug);
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
mount_to_body(|cx| {
|
|
||||||
view! { cx, <App/> }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::errors::TodoAppError;
|
use crate::errors::TodoAppError;
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use leptos::Errors;
|
use leptos::Errors;
|
||||||
use leptos::{
|
use leptos::*;
|
||||||
component, create_rw_signal, use_context, view, For, ForProps, IntoView, RwSignal, Scope,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use leptos_axum::ResponseOptions;
|
use leptos_axum::ResponseOptions;
|
||||||
|
|
||||||
|
@ -16,10 +15,7 @@ pub fn ErrorTemplate(
|
||||||
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
#[prop(optional)] errors: Option<RwSignal<Errors>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let errors = match outside_errors {
|
let errors = match outside_errors {
|
||||||
Some(e) => {
|
Some(e) => create_rw_signal(cx, e),
|
||||||
let errors = create_rw_signal(cx, e);
|
|
||||||
errors
|
|
||||||
}
|
|
||||||
None => match errors {
|
None => match errors {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => panic!("No Errors found and we expected errors!"),
|
None => panic!("No Errors found and we expected errors!"),
|
||||||
|
@ -32,8 +28,7 @@ pub fn ErrorTemplate(
|
||||||
// Downcast lets us take a type that implements `std::error::Error`
|
// Downcast lets us take a type that implements `std::error::Error`
|
||||||
let errors: Vec<TodoAppError> = errors
|
let errors: Vec<TodoAppError> = errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_k, v)| v.downcast_ref::<TodoAppError>().cloned())
|
.filter_map(|(_k, v)| v.downcast_ref::<TodoAppError>().cloned())
|
||||||
.flatten()
|
|
||||||
.collect();
|
.collect();
|
||||||
println!("Errors: {errors:#?}");
|
println!("Errors: {errors:#?}");
|
||||||
|
|
||||||
|
@ -54,7 +49,7 @@ pub fn ErrorTemplate(
|
||||||
// a function that returns the items we're iterating over; a signal is fine
|
// a function that returns the items we're iterating over; a signal is fine
|
||||||
each= move || {errors.clone().into_iter().enumerate()}
|
each= move || {errors.clone().into_iter().enumerate()}
|
||||||
// a unique key for each item as a reference
|
// a unique key for each item as a reference
|
||||||
key=|(index, _error)| index.clone()
|
key=|(index, _error)| *index
|
||||||
// renders each item to a view
|
// renders each item to a view
|
||||||
view= move |error| {
|
view= move |error| {
|
||||||
let error_string = error.1.to_string();
|
let error_string = error.1.to_string();
|
||||||
|
|
|
@ -33,14 +33,13 @@ if #[cfg(feature = "ssr")] {
|
||||||
|
|
||||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||||
let root_path = format!("{root}");
|
|
||||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||||
// This path is relative to the cargo root
|
// This path is relative to the cargo root
|
||||||
match ServeDir::new(&root_path).oneshot(req).await {
|
match ServeDir::new(root).oneshot(req).await {
|
||||||
Ok(res) => Ok(res.map(boxed)),
|
Ok(res) => Ok(res.map(boxed)),
|
||||||
Err(err) => Err((
|
Err(err) => Err((
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("Something went wrong: {}", err),
|
format!("Something went wrong: {err}"),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ if #[cfg(feature = "ssr")] {
|
||||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||||
let conf = get_configuration(None).await.unwrap();
|
let conf = get_configuration(None).await.unwrap();
|
||||||
let leptos_options = conf.leptos_options;
|
let leptos_options = conf.leptos_options;
|
||||||
let addr = leptos_options.site_address.clone();
|
let addr = leptos_options.site_address;
|
||||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||||
|
|
||||||
// build our application with a route
|
// build our application with a route
|
||||||
|
@ -56,7 +56,7 @@ if #[cfg(feature = "ssr")] {
|
||||||
|
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
log!("listening on {}", &addr);
|
log!("listening on http://{}", &addr);
|
||||||
axum::Server::bind(&addr)
|
axum::Server::bind(&addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await
|
.await
|
||||||
|
@ -66,15 +66,9 @@ if #[cfg(feature = "ssr")] {
|
||||||
|
|
||||||
// client-only stuff for Trunk
|
// client-only stuff for Trunk
|
||||||
else {
|
else {
|
||||||
use todo_app_sqlite_axum::todo::*;
|
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
console_error_panic_hook::set_once();
|
// This example cannot be built as a trunk standalone CSR-only app.
|
||||||
_ = console_log::init_with_level(log::Level::Debug);
|
// Only the server may directly connect to the database.
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
mount_to_body(|cx| {
|
|
||||||
view! { cx, <TodoApp/> }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ cfg_if! {
|
||||||
// use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode};
|
// use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode};
|
||||||
|
|
||||||
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
|
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
|
||||||
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
|
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_server_functions() {
|
pub fn register_server_functions() {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue