|
@@ -2,209 +2,83 @@
|
|
|
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
|
|
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
|
|
|
|
|
-use convert_case::{Case, Converter};
|
|
|
+//! This crate contains the dioxus implementation of the #[macro@crate::server] macro without additional context from the server.
|
|
|
+//! See the [server_fn_macro] crate for more information.
|
|
|
+
|
|
|
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,
|
|
|
-};
|
|
|
+use server_fn_macro::server_macro_impl;
|
|
|
+use syn::__private::ToTokens;
|
|
|
|
|
|
-/// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
|
|
|
-/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
|
|
|
+/// Declares that a function is a [server function](https://docs.rs/server_fn/).
|
|
|
+/// This means that its body will only run on the server, i.e., when the `ssr`
|
|
|
+/// feature is enabled on this crate.
|
|
|
///
|
|
|
-/// 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.
|
|
|
+/// ## Usage
|
|
|
+/// ```rust,ignore
|
|
|
+/// #[server]
|
|
|
+/// pub async fn blog_posts(
|
|
|
+/// category: String,
|
|
|
+/// ) -> Result<Vec<BlogPost>, ServerFnError> {
|
|
|
+/// let posts = load_posts(&category).await?;
|
|
|
+/// // maybe do some other work
|
|
|
+/// Ok(posts)
|
|
|
+/// }
|
|
|
+/// ```
|
|
|
///
|
|
|
-/// You can specify one, two, or three arguments to the server function:
|
|
|
-/// 1. *Optional*: 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"`.
|
|
|
+/// ## Named Arguments
|
|
|
///
|
|
|
-/// 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.
|
|
|
+/// You can any combination of the following named arguments:
|
|
|
+/// - `name`: sets the identifier for the server function’s type, which is a struct created
|
|
|
+/// to hold the arguments (defaults to the function identifier in PascalCase)
|
|
|
+/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
|
|
|
+/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
|
|
|
+/// relative to the prefix (defaults to the function name followed by unique hash)
|
|
|
+/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
|
|
|
+/// - `output`: the encoding for the response (defaults to `Json`)
|
|
|
+/// - `client`: a custom `Client` implementation that will be used for this server fn
|
|
|
+/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
|
|
|
+/// of the following (not case sensitive)
|
|
|
+/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
|
|
|
+/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
|
|
|
+/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
|
|
|
+/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
|
|
|
+/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
|
|
|
+/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
|
|
|
+/// ```rust,ignore
|
|
|
+/// #[server(
|
|
|
+/// name = SomeStructName,
|
|
|
+/// prefix = "/my_api",
|
|
|
+/// endpoint = "my_fn",
|
|
|
+/// input = Cbor,
|
|
|
+/// output = Json
|
|
|
+/// )]
|
|
|
+/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
|
|
|
+/// todo!()
|
|
|
+/// }
|
|
|
///
|
|
|
-/// ```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<Vec<Post>, ServerFnError> {
|
|
|
-/// // do some work on the server to access the database
|
|
|
-/// # unimplemented!()
|
|
|
+/// // expands to
|
|
|
+/// #[derive(Deserialize, Serialize)]
|
|
|
+/// struct SomeStructName {
|
|
|
+/// input: Vec<String>
|
|
|
/// }
|
|
|
-/// ```
|
|
|
///
|
|
|
-/// 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.
|
|
|
-/// - **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.
|
|
|
+/// impl ServerFn for SomeStructName {
|
|
|
+/// const PATH: &'static str = "/my_api/my_fn";
|
|
|
+///
|
|
|
+/// // etc.
|
|
|
+/// }
|
|
|
+/// ```
|
|
|
#[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<Middleware> = 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 = "server")]
|
|
|
- #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<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,
|
|
|
- })
|
|
|
- }
|
|
|
+ match server_macro_impl(
|
|
|
+ args.into(),
|
|
|
+ s.into(),
|
|
|
+ Some(syn::parse_quote!(server_fn)),
|
|
|
+ "/api",
|
|
|
+ None,
|
|
|
+ None,
|
|
|
+ ) {
|
|
|
+ Err(e) => e.to_compile_error().into(),
|
|
|
+ Ok(s) => s.to_token_stream().into(),
|
|
|
+ }
|
|
|
}
|