2023-10-10 15:52:48 +00:00
// TODO: Create README, uncomment this: #![doc = include_str!("../README.md")]
#![ doc(html_logo_url = " https://avatars.githubusercontent.com/u/79236386 " ) ]
#![ doc(html_favicon_url = " https://avatars.githubusercontent.com/u/79236386 " ) ]
2023-07-08 01:25:45 +00:00
use convert_case ::{ Case , Converter } ;
2023-03-30 15:34:13 +00:00
use proc_macro ::TokenStream ;
2023-07-08 01:25:45 +00:00
use proc_macro2 ::Literal ;
2023-07-07 18:03:59 +00:00
use quote ::{ ToTokens , __private ::TokenStream as TokenStream2 } ;
2023-03-30 15:34:13 +00:00
use server_fn_macro ::* ;
2023-07-08 01:25:45 +00:00
use syn ::{
parse ::{ Parse , ParseStream } ,
Ident , ItemFn , Token ,
} ;
2023-03-30 15:34:13 +00:00
2023-09-25 13:31:35 +00:00
/// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
2023-03-28 18:35:17 +00:00
/// 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
2023-04-28 01:25:16 +00:00
/// serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string).
2023-05-02 16:05:21 +00:00
/// Defaults to `"Url"`. If you want to use this server function
2023-04-28 01:25:16 +00:00
/// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`.
2023-03-28 18:35:17 +00:00
///
/// The server function itself can take any number of arguments, each of which should be serializable
2023-05-02 15:15:34 +00:00
/// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html),
2023-03-28 18:35:17 +00:00
/// 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
2023-05-02 15:15:34 +00:00
/// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize};
2023-03-28 18:35:17 +00:00
/// # #[derive(Serialize, Deserialize)]
/// # pub struct Post { }
/// #[server(ReadPosts, "/api")]
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
/// // do some work on the server to access the database
2023-04-01 22:00:12 +00:00
/// todo!()
2023-03-28 18:35:17 +00:00
/// }
/// ```
///
/// 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<T, ServerFnError>`.** Even if the work being done
/// inside the function body can’ t fail, the processes of serialization/deserialization and the
/// network call are fallible.
2023-04-01 22:00:12 +00:00
/// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
2023-03-28 18:35:17 +00:00
/// 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/).
2023-05-02 15:15:34 +00:00
/// - **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
2023-03-28 18:35:17 +00:00
/// 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 {
2023-07-07 18:03:59 +00:00
// 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 ( ) ,
} ;
2023-07-08 01:25:45 +00:00
// extract all #[middleware] attributes
let mut middlewares : Vec < Middleware > = vec! [ ] ;
2023-07-08 01:41:22 +00:00
function . attrs . retain ( | attr | {
2023-07-08 02:05:15 +00:00
if attr . meta . path ( ) . is_ident ( " middleware " ) {
if let Ok ( middleware ) = attr . parse_args ( ) {
middlewares . push ( middleware ) ;
false
2023-07-08 01:25:45 +00:00
} else {
true
}
2023-07-08 02:05:15 +00:00
} else {
true
}
} ) ;
2023-07-08 01:25:45 +00:00
2023-07-07 18:03:59 +00:00
let ItemFn {
attrs ,
vis ,
sig ,
block ,
} = function ;
let mapped_body = quote ::quote! {
#( #attrs ) *
#vis #sig {
#block
}
} ;
2023-07-08 01:25:45 +00:00
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 )
2023-10-14 19:32:08 +00:00
. convert ( sig . ident . to_string ( ) ) ;
2023-07-11 00:34:45 +00:00
args . struct_name = Some ( Ident ::new ( & upper_cammel_case_name , sig . ident . span ( ) ) ) ;
2023-07-08 01:25:45 +00:00
}
let struct_name = args . struct_name . as_ref ( ) . unwrap ( ) ;
2023-03-28 18:35:17 +00:00
match server_macro_impl (
2023-07-08 01:25:45 +00:00
quote ::quote! ( #args ) ,
2023-07-07 18:03:59 +00:00
mapped_body ,
2023-07-08 01:41:22 +00:00
trait_obj_wrapper ,
2023-07-07 00:54:05 +00:00
None ,
2023-07-08 01:25:45 +00:00
Some ( server_fn_path . clone ( ) ) ,
2023-03-28 18:35:17 +00:00
) {
Err ( e ) = > e . to_compile_error ( ) . into ( ) ,
2023-07-08 01:25:45 +00:00
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 ( ) ,
2023-03-28 18:35:17 +00:00
}
}
2023-07-07 18:03:59 +00:00
2023-07-08 01:25:45 +00:00
#[ 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 < Self > {
let arg : syn ::Expr = input . parse ( ) ? ;
Ok ( Middleware { expr : arg } )
}
}
struct ServerFnArgs {
struct_name : Option < Ident > ,
_comma : Option < Token! [ , ] > ,
prefix : Option < Literal > ,
_comma2 : Option < Token! [ , ] > ,
encoding : Option < Literal > ,
_comma3 : Option < Token! [ , ] > ,
fn_path : Option < Literal > ,
}
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 < Self > {
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 ,
} )
}
}