From 607da92c7191c5330c237aa07e0589abddb21ac3 Mon Sep 17 00:00:00 2001 From: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com> Date: Mon, 11 Nov 2024 21:52:22 +0100 Subject: [PATCH] Make handler arguments optional (#87) * feat: support axum extractors * chore: update tuono example * chore: update tutorial example * doc: update documentation * feat: update version to v0.12.0 * fix: use clippy suggestion --- .../documentation/tutorial/api-fetching.mdx | 4 ++ .../documentation/tutorial/redirections.mdx | 4 +- crates/tuono/Cargo.toml | 2 +- crates/tuono_lib/Cargo.toml | 4 +- crates/tuono_lib_macros/Cargo.toml | 2 +- crates/tuono_lib_macros/src/handler.rs | 69 ++++++++++++++----- examples/tuono/src/routes/index.rs | 3 +- examples/tutorial/src/routes/pokemons/GOAT.rs | 4 +- packages/fs-router-vite-plugin/package.json | 2 +- packages/lazy-fn-vite-plugin/package.json | 2 +- packages/router/package.json | 2 +- packages/tuono/package.json | 2 +- 12 files changed, 70 insertions(+), 30 deletions(-) diff --git a/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx b/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx index b4ae1d9b..098c9a25 100644 --- a/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx @@ -56,6 +56,10 @@ async fn get_all_pokemons(_req: Request, fetch: Client) -> Response { } ``` +> The first argument is always the request `req: Request` which contains the request data like the query parameters and the headers. +The rest of the arguments are optional and they don't need to be specified +if they are not used. Enabled out of the box a [Reqwest](https://docs.rs/reqwest/latest/reqwest/) HTTP client. + Now the Pokémon are correctly fetched and hydrated on the client side, so we can actually use them. Clear the `index.tsx` file and paste: ```tsx diff --git a/apps/documentation/src/routes/documentation/tutorial/redirections.mdx b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx index 416fd6fe..37ec8f08 100644 --- a/apps/documentation/src/routes/documentation/tutorial/redirections.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx @@ -25,10 +25,10 @@ First, let's create a new route by just creating a new file `/pokemons/GOAT.rs` ```rs // src/routes/pokemons/GOAT.rs -use tuono_lib::{reqwest::Client, Request, Response}; +use tuono_lib::{Request, Response}; #[tuono_lib::handler] -async fn redirect_to_goat(_: Request, _: Client) -> Response { +async fn redirect_to_goat(_req: Request) -> Response { // Of course the GOAT is mewtwo - feel free to select your favourite 😉 Response::Redirect("/pokemons/mewtwo".to_string()) } diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index 5bf5bd75..997657e6 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono" -version = "0.11.2" +version = "0.12.0" edition = "2021" authors = ["V. Ageno "] description = "The react/rust fullstack framework" diff --git a/crates/tuono_lib/Cargo.toml b/crates/tuono_lib/Cargo.toml index 0cc644b5..1f47d3fe 100644 --- a/crates/tuono_lib/Cargo.toml +++ b/crates/tuono_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono_lib" -version = "0.11.2" +version = "0.12.0" edition = "2021" authors = ["V. Ageno "] description = "The react/rust fullstack framework" @@ -33,7 +33,7 @@ either = "1.13.0" tower-http = {version = "0.6.0", features = ["fs"]} colored = "2.1.0" -tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.11.2"} +tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.12.0"} # Match the same version used by axum tokio-tungstenite = "0.24.0" futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } diff --git a/crates/tuono_lib_macros/Cargo.toml b/crates/tuono_lib_macros/Cargo.toml index 20c5f126..f16bef70 100644 --- a/crates/tuono_lib_macros/Cargo.toml +++ b/crates/tuono_lib_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuono_lib_macros" -version = "0.11.2" +version = "0.12.0" edition = "2021" description = "The react/rust fullstack framework" keywords = [ "react", "typescript", "fullstack", "web", "ssr"] diff --git a/crates/tuono_lib_macros/src/handler.rs b/crates/tuono_lib_macros/src/handler.rs index 7cd2d5bc..bb87b329 100644 --- a/crates/tuono_lib_macros/src/handler.rs +++ b/crates/tuono_lib_macros/src/handler.rs @@ -1,43 +1,80 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemFn}; +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{parse2, parse_macro_input, FnArg, ItemFn, Pat, Type}; +fn create_struct_fn_arg(arg_name: Pat, arg_type: Type) -> FnArg { + parse2(quote! { + tuono_lib::axum::extract::State(#arg_name): tuono_lib::axum::extract::State<#arg_type> + }) + .unwrap() +} + +fn params_argument() -> FnArg { + parse2(quote! { + tuono_lib::axum::extract::Path(params): tuono_lib::axum::extract::Path< + std::collections::HashMap + > + }) + .unwrap() +} + +fn request_argument() -> FnArg { + parse2(quote! { + request: tuono_lib::axum::extract::Request + }) + .unwrap() +} pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as ItemFn); - let fn_name = item.clone().sig.ident; + let fn_name = &item.sig.ident; - quote! { - use tuono_lib::axum::response::IntoResponse; - use std::collections::HashMap; - use tuono_lib::axum::extract::{State, Path}; + let mut argument_names: Punctuated = Punctuated::new(); + let mut axum_arguments: Punctuated = Punctuated::new(); + + // Fn Arguments minus the first which always is the request + for (i, arg) in item.sig.inputs.iter().enumerate() { + if i == 0 { + axum_arguments.insert(i, params_argument()); + continue; + } + + if let FnArg::Typed(pat_type) = arg { + let index = i - 1; + let argument_name = *pat_type.pat.clone(); + let argument_type = *pat_type.ty.clone(); + argument_names.insert(index, argument_name.clone()); + axum_arguments.insert(index, create_struct_fn_arg(argument_name, argument_type)) + } + } + axum_arguments.insert(axum_arguments.len(), request_argument()); + + quote! { #item pub async fn route( - Path(params): Path>, - State(client): State, - request: tuono_lib::axum::extract::Request - ) -> impl IntoResponse { + #axum_arguments + ) -> impl tuono_lib::axum::response::IntoResponse { let pathname = request.uri(); let headers = request.headers(); let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params); - #fn_name(req.clone(), client).await.render_to_string(req) + #fn_name(req.clone(), #argument_names).await.render_to_string(req) } pub async fn api( - Path(params): Path>, - State(client): State, - request: tuono_lib::axum::extract::Request - ) -> impl IntoResponse{ + #axum_arguments + ) -> impl tuono_lib::axum::response::IntoResponse { let pathname = request.uri(); let headers = request.headers(); let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params); - #fn_name(req.clone(), client).await.json() + #fn_name(req.clone(), #argument_names).await.json() } } .into() diff --git a/examples/tuono/src/routes/index.rs b/examples/tuono/src/routes/index.rs index f9217c02..35d70bd3 100644 --- a/examples/tuono/src/routes/index.rs +++ b/examples/tuono/src/routes/index.rs @@ -1,5 +1,4 @@ use serde::Serialize; -use tuono_lib::reqwest; use tuono_lib::{Props, Request, Response}; #[derive(Serialize)] @@ -8,7 +7,7 @@ struct MyResponse<'a> { } #[tuono_lib::handler] -async fn get_server_side_props(_req: Request, _fetch: reqwest::Client) -> Response { +async fn get_server_side_props(_req: Request) -> Response { Response::Props(Props::new(MyResponse { subtitle: "The react / rust fullstack framework", })) diff --git a/examples/tutorial/src/routes/pokemons/GOAT.rs b/examples/tutorial/src/routes/pokemons/GOAT.rs index 9a990997..e4f83070 100644 --- a/examples/tutorial/src/routes/pokemons/GOAT.rs +++ b/examples/tutorial/src/routes/pokemons/GOAT.rs @@ -1,7 +1,7 @@ // src/routes/pokemons/GOAT.rs -use tuono_lib::{reqwest::Client, Request, Response}; +use tuono_lib::{Request, Response}; #[tuono_lib::handler] -async fn redirect_to_goat(_: Request, _: Client) -> Response { +async fn redirect_to_goat(_req: Request) -> Response { Response::Redirect("/pokemons/mewtwo".to_string()) } diff --git a/packages/fs-router-vite-plugin/package.json b/packages/fs-router-vite-plugin/package.json index 25709a01..d15e5414 100644 --- a/packages/fs-router-vite-plugin/package.json +++ b/packages/fs-router-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "tuono-fs-router-vite-plugin", - "version": "0.11.2", + "version": "0.12.0", "description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/lazy-fn-vite-plugin/package.json b/packages/lazy-fn-vite-plugin/package.json index f59a48cb..925f6122 100644 --- a/packages/lazy-fn-vite-plugin/package.json +++ b/packages/lazy-fn-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "tuono-lazy-fn-vite-plugin", - "version": "0.11.2", + "version": "0.12.0", "description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/router/package.json b/packages/router/package.json index 01eccb68..1724aa47 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "tuono-router", - "version": "0.11.2", + "version": "0.12.0", "description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework", "scripts": { "dev": "vite build --watch", diff --git a/packages/tuono/package.json b/packages/tuono/package.json index 4a0459fc..47bb205f 100644 --- a/packages/tuono/package.json +++ b/packages/tuono/package.json @@ -1,6 +1,6 @@ { "name": "tuono", - "version": "0.11.2", + "version": "0.12.0", "description": "The react/rust fullstack framework", "scripts": { "dev": "vite build --watch",