From e0fc19ecaf9461973f2425d67b20adf4a52e4d3e Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sat, 15 Jun 2024 14:17:12 +0200 Subject: [PATCH] doc: update tutorial --- crates/tuono_lib_macros/src/handler.rs | 2 +- docs/tutorial.md | 111 +++++++++++++++--- examples/tutorial/src/routes/index.rs | 2 +- .../tutorial/src/routes/pokemons/[pokemon].rs | 2 +- .../src/routes/pokemons/[pokemon].tsx | 13 +- 5 files changed, 109 insertions(+), 21 deletions(-) diff --git a/crates/tuono_lib_macros/src/handler.rs b/crates/tuono_lib_macros/src/handler.rs index 44b3385b..0c8f2bae 100644 --- a/crates/tuono_lib_macros/src/handler.rs +++ b/crates/tuono_lib_macros/src/handler.rs @@ -8,7 +8,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream { let fn_name = item.clone().sig.ident; quote! { - use axum::response::{Html, IntoResponse}; + use axum::response::IntoResponse; use std::collections::HashMap; use axum::extract::{State, Path}; use reqwest::Client; diff --git a/docs/tutorial.md b/docs/tutorial.md index be7e26a2..ad03e37e 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -93,7 +93,7 @@ Clear the `index.rs` file and paste: ```rust // src/routes/index.rs use serde::{Deserialize, Serialize}; -use tuono_lib::{Request, Response}; +use tuono_lib::{Props, Request, Response}; const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151"; @@ -110,15 +110,13 @@ struct Pokemon { #[tuono_lib::handler] async fn get_all_pokemons(_req: Request<'_>, fetch: reqwest::Client) -> Response { - return match fetch.get(ALL_POKEMON).send().await { Ok(res) => { let data = res.json::().await.unwrap(); - Response::Props(Box::new(data)) + Response::Props(Props::new(data)) } - Err(_err) => Response::Props(Box::new(Pokemons { results: vec![] })), + Err(_err) => Response::Props(Props::new("{}")), }; - } ``` @@ -302,8 +300,9 @@ These two will handle every requests that points to `http://localhost:3000/pokem Let’s first work on the server side file. Paste into the new `[pokemon].rs` file the following code: ```rust +// src/routes/pokemons/[pokemon].rs use serde::{Deserialize, Serialize}; -use tuono_lib::{Request, Response}; +use tuono_lib::{Props, Request, Response}; const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon"; @@ -317,22 +316,16 @@ struct Pokemon { #[tuono_lib::handler] async fn get_pokemon(req: Request<'_>, fetch: reqwest::Client) -> Response { - // The param `pokemon` is defined in the route filename [pokemon].rs + // The param `pokemon` is defined in the route filename [pokemon].rs let pokemon = req.params.get("pokemon").unwrap(); - + return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await { Ok(res) => { let data = res.json::().await.unwrap(); - Response::Props(Box::new(data)) + Response::Props(Props::new(data)) } - Err(_err) => Response::Props(Box::new(Pokemon { - name: "Nope".to_string(), - id: 0, - weight: 0, - height: 0, - })), + Err(_err) => Response::Props(Props::new("{}")) }; - } ``` @@ -438,6 +431,92 @@ export default function PokemonView({ } ``` +## Error handling + +With the current setup all the routes always return a `200 Success` http status no matter the response type. + +In order to return a more meaningful status code to the browser the `Props` struct can be initialized with also the +`Props::new_with_status()` method. + +Let's see how it works! + +```diff +// src/routes/pokemons/[pokemon].rs +++ use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; +use tuono_lib::{Props, Request, Response}; + +const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon"; + +#[derive(Debug, Serialize, Deserialize)] +struct Pokemon { + name: String, + id: u16, + weight: u16, + height: u16, +} + +#[tuono_lib::handler] +async fn get_pokemon(req: Request<'_>, fetch: reqwest::Client) -> Response { + // The param `pokemon` is defined in the route filename [pokemon].rs + let pokemon = req.params.get("pokemon").unwrap(); + + return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await { + Ok(res) => { +++ if res.status() == StatusCode::NOT_FOUND { +++ return Response::Props(Props::new_with_status("{}", StatusCode::NOT_FOUND)); +++ } + + let data = res.json::().await.unwrap(); + Response::Props(Props::new(data)) + } +-- Err(_err) => Response::Props(Props::new( +++ Err(_err) => Response::Props(Props::new_with_status( +++ "{}", +++ StatusCode::INTERNAL_SERVER_ERROR, + )), + }; +} +``` + +```diff +// src/routes/index.rs +++ use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; +use tuono_lib::{Props, Request, Response}; + +const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151"; + +#[derive(Debug, Serialize, Deserialize)] +struct Pokemons { + results: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Pokemon { + name: String, + url: String, +} + +#[tuono_lib::handler] +async fn get_all_pokemons(_req: Request<'_>, fetch: reqwest::Client) -> Response { + return match fetch.get(ALL_POKEMON).send().await { + Ok(res) => { + let data = res.json::().await.unwrap(); + Response::Props(Props::new(data)) + } +-- Err(_err) => Response::Props(Props::new( +++ Err(_err) => Response::Props(Props::new_with_status( +++ "{}", // Return empty JSON +++ StatusCode::INTERNAL_SERVER_ERROR, + )), + }; +} +``` + +If you now try to load a not existing pokemon (`http://localhost:3000/pokemons/tuono-pokemon`) you will +correctly receive a 404 status code in the console. + ## Conclusion That’s it! You just created a multi thread full stack application with rust and react. diff --git a/examples/tutorial/src/routes/index.rs b/examples/tutorial/src/routes/index.rs index 1c2ed45a..76833288 100644 --- a/examples/tutorial/src/routes/index.rs +++ b/examples/tutorial/src/routes/index.rs @@ -1,5 +1,5 @@ -use reqwest::StatusCode; // src/routes/index.rs +use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use tuono_lib::{Props, Request, Response}; diff --git a/examples/tutorial/src/routes/pokemons/[pokemon].rs b/examples/tutorial/src/routes/pokemons/[pokemon].rs index 7b6275d7..756cbf7a 100644 --- a/examples/tutorial/src/routes/pokemons/[pokemon].rs +++ b/examples/tutorial/src/routes/pokemons/[pokemon].rs @@ -1,3 +1,4 @@ +// src/routes/pokemons/[pokemon].rs use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use tuono_lib::{Props, Request, Response}; @@ -22,7 +23,6 @@ async fn get_pokemon(req: Request<'_>, fetch: reqwest::Client) -> Response { if res.status() == StatusCode::NOT_FOUND { return Response::Props(Props::new_with_status("{}", StatusCode::NOT_FOUND)); } - let data = res.json::().await.unwrap(); Response::Props(Props::new(data)) } diff --git a/examples/tutorial/src/routes/pokemons/[pokemon].tsx b/examples/tutorial/src/routes/pokemons/[pokemon].tsx index 7cf77395..b480a6fe 100644 --- a/examples/tutorial/src/routes/pokemons/[pokemon].tsx +++ b/examples/tutorial/src/routes/pokemons/[pokemon].tsx @@ -1,6 +1,15 @@ -import { TuonoProps } from 'tuono' +import type { TuonoProps } from 'tuono' import PokemonView from '../../components/PokemonView' -export default function Pokemon({ data }: TuonoProps): JSX.Element { +interface Pokemon { + name: string + id: string + weight: number + height: number +} + +export default function PokemonPage({ + data, +}: TuonoProps): JSX.Element { return }