use convert_case::{Case, Converter}; use proc_macro::TokenStream; use proc_macro2::Literal; use quote::{ToTokens, __private::TokenStream as TokenStream2}; use server_fn_macro::*; use syn::{ parse::{Parse, ParseStream}, Ident, ItemFn, Token, }; /// Declares that a function is a [server function](dioxus_fullstack). This means that /// its body will only run on the server, i.e., when the `ssr` feature is enabled. /// /// If you call a server function from the client (i.e., when the `csr` or `hydrate` features /// are enabled), it will instead make a network request to the server. /// /// You can specify one, two, or three arguments to the server function: /// 1. **Required**: A type name that will be used to identify and register the server function /// (e.g., `MyServerFn`). /// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered /// (e.g., `"/api"`). Defaults to `"/"`. /// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for /// serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string). /// Defaults to `"Url"`. If you want to use this server function /// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`. /// /// The server function itself can take any number of arguments, each of which should be serializable /// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html), /// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other /// server-side context into the server function. /// /// ```ignore /// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize}; /// # #[derive(Serialize, Deserialize)] /// # pub struct Post { } /// #[server(ReadPosts, "/api")] /// pub async fn read_posts(how_many: u8, query: String) -> Result, ServerFnError> { /// // do some work on the server to access the database /// todo!() /// } /// ``` /// /// Note the following: /// - **Server functions must be `async`.** Even if the work being done inside the function body /// can run synchronously on the server, from the client’s perspective it involves an asynchronous /// function call. /// - **Server functions must return `Result`.** Even if the work being done /// inside the function body can’t fail, the processes of serialization/deserialization and the /// network call are fallible. /// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).** /// This should be fairly obvious: we have to serialize arguments to send them to the server, and we /// need to deserialize the result to return it to the client. /// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) /// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).** /// They are serialized as an `application/x-www-form-urlencoded` /// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor` /// using [`cbor`](https://docs.rs/cbor/latest/cbor/). /// - **The [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html) comes from the server.** Optionally, the first argument of a server function /// can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html). This scope can be used to inject dependencies like the HTTP request /// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client. #[proc_macro_attribute] pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { // before we pass this off to the server function macro, we apply extractors and middleware let mut function: syn::ItemFn = match syn::parse(s).map_err(|e| e.to_compile_error()) { Ok(f) => f, Err(e) => return e.into(), }; // extract all #[middleware] attributes let mut middlewares: Vec = vec![]; function.attrs.retain(|attr| { if attr.meta.path().is_ident("middleware") { if let Ok(middleware) = attr.parse_args() { middlewares.push(middleware); false } else { true } } else { true } }); let ItemFn { attrs, vis, sig, block, } = function; let mapped_body = quote::quote! { #(#attrs)* #vis #sig { #block } }; let server_fn_path: syn::Path = syn::parse_quote!(::dioxus_fullstack::prelude::server_fn); let trait_obj_wrapper: syn::Type = syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj); let mut args: ServerFnArgs = match syn::parse(args) { Ok(args) => args, Err(e) => return e.to_compile_error().into(), }; if args.struct_name.is_none() { let upper_cammel_case_name = Converter::new() .from_case(Case::Snake) .to_case(Case::UpperCamel) .convert(&sig.ident.to_string()); args.struct_name = Some(Ident::new(&upper_cammel_case_name, sig.ident.span())); } let struct_name = args.struct_name.as_ref().unwrap(); match server_macro_impl( quote::quote!(#args), mapped_body, trait_obj_wrapper, None, Some(server_fn_path.clone()), ) { Err(e) => e.to_compile_error().into(), Ok(tokens) => quote::quote! { #tokens #[cfg(feature = "ssr")] #server_fn_path::inventory::submit! { ::dioxus_fullstack::prelude::ServerFnMiddleware { prefix: #struct_name::PREFIX, url: #struct_name::URL, middleware: || vec![ #( std::sync::Arc::new(#middlewares), ),* ] } } } .to_token_stream() .into(), } } #[derive(Debug)] struct Middleware { expr: syn::Expr, } impl ToTokens for Middleware { fn to_tokens(&self, tokens: &mut TokenStream2) { let expr = &self.expr; tokens.extend(quote::quote! { #expr }); } } impl Parse for Middleware { fn parse(input: syn::parse::ParseStream) -> syn::Result { let arg: syn::Expr = input.parse()?; Ok(Middleware { expr: arg }) } } struct ServerFnArgs { struct_name: Option, _comma: Option, prefix: Option, _comma2: Option, encoding: Option, _comma3: Option, fn_path: Option, } impl ToTokens for ServerFnArgs { fn to_tokens(&self, tokens: &mut TokenStream2) { let struct_name = self.struct_name.as_ref().map(|s| quote::quote! { #s, }); let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, }); let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, }); let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, }); tokens.extend(quote::quote! { #struct_name #prefix #encoding #fn_path }) } } impl Parse for ServerFnArgs { fn parse(input: ParseStream) -> syn::Result { let struct_name = input.parse()?; let _comma = input.parse()?; let prefix = input.parse()?; let _comma2 = input.parse()?; let encoding = input.parse()?; let _comma3 = input.parse()?; let fn_path = input.parse()?; Ok(Self { struct_name, _comma, prefix, _comma2, encoding, _comma3, fn_path, }) } }