lib.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // TODO: Create README, uncomment this: #![doc = include_str!("../README.md")]
  2. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  4. use convert_case::{Case, Converter};
  5. use proc_macro::TokenStream;
  6. use proc_macro2::Literal;
  7. use quote::{ToTokens, __private::TokenStream as TokenStream2};
  8. use server_fn_macro::*;
  9. use syn::{
  10. parse::{Parse, ParseStream},
  11. Ident, ItemFn, Token,
  12. };
  13. /// Declares that a function is a [server function](https://dioxuslabs.com/learn/0.4/reference/fullstack/server_functions). This means that
  14. /// its body will only run on the server, i.e., when the `ssr` feature is enabled.
  15. ///
  16. /// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
  17. /// are enabled), it will instead make a network request to the server.
  18. ///
  19. /// You can specify one, two, or three arguments to the server function:
  20. /// 1. **Required**: A type name that will be used to identify and register the server function
  21. /// (e.g., `MyServerFn`).
  22. /// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
  23. /// (e.g., `"/api"`). Defaults to `"/"`.
  24. /// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
  25. /// serialization), `"Url"` (specifying that it should be use a URL-encoded form-data string).
  26. /// Defaults to `"Url"`. If you want to use this server function
  27. /// using Get instead of Post methods, the encoding must be `"GetCbor"` or `"GetJson"`.
  28. ///
  29. /// The server function itself can take any number of arguments, each of which should be serializable
  30. /// and deserializable with `serde`. Optionally, its first argument can be a [DioxusServerContext](https::/docs.rs/dioxus-fullstack/latest/dixous_server/prelude/struct.DioxusServerContext.html),
  31. /// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
  32. /// server-side context into the server function.
  33. ///
  34. /// ```ignore
  35. /// # use dioxus_fullstack::prelude::*; use serde::{Serialize, Deserialize};
  36. /// # #[derive(Serialize, Deserialize)]
  37. /// # pub struct Post { }
  38. /// #[server(ReadPosts, "/api")]
  39. /// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
  40. /// // do some work on the server to access the database
  41. /// todo!()
  42. /// }
  43. /// ```
  44. ///
  45. /// Note the following:
  46. /// - **Server functions must be `async`.** Even if the work being done inside the function body
  47. /// can run synchronously on the server, from the client’s perspective it involves an asynchronous
  48. /// function call.
  49. /// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
  50. /// inside the function body can’t fail, the processes of serialization/deserialization and the
  51. /// network call are fallible.
  52. /// - **Return types must implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html).**
  53. /// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
  54. /// need to deserialize the result to return it to the client.
  55. /// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
  56. /// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
  57. /// They are serialized as an `application/x-www-form-urlencoded`
  58. /// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
  59. /// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
  60. /// - **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
  61. /// 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
  62. /// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
  63. #[proc_macro_attribute]
  64. pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
  65. // before we pass this off to the server function macro, we apply extractors and middleware
  66. let mut function: syn::ItemFn = match syn::parse(s).map_err(|e| e.to_compile_error()) {
  67. Ok(f) => f,
  68. Err(e) => return e.into(),
  69. };
  70. // extract all #[middleware] attributes
  71. let mut middlewares: Vec<Middleware> = vec![];
  72. function.attrs.retain(|attr| {
  73. if attr.meta.path().is_ident("middleware") {
  74. if let Ok(middleware) = attr.parse_args() {
  75. middlewares.push(middleware);
  76. false
  77. } else {
  78. true
  79. }
  80. } else {
  81. true
  82. }
  83. });
  84. let ItemFn {
  85. attrs,
  86. vis,
  87. sig,
  88. block,
  89. } = function;
  90. let mapped_body = quote::quote! {
  91. #(#attrs)*
  92. #vis #sig {
  93. #block
  94. }
  95. };
  96. let server_fn_path: syn::Path = syn::parse_quote!(::dioxus_fullstack::prelude::server_fn);
  97. let trait_obj_wrapper: syn::Type =
  98. syn::parse_quote!(::dioxus_fullstack::prelude::ServerFnTraitObj);
  99. let mut args: ServerFnArgs = match syn::parse(args) {
  100. Ok(args) => args,
  101. Err(e) => return e.to_compile_error().into(),
  102. };
  103. if args.struct_name.is_none() {
  104. let upper_cammel_case_name = Converter::new()
  105. .from_case(Case::Snake)
  106. .to_case(Case::UpperCamel)
  107. .convert(&sig.ident.to_string());
  108. args.struct_name = Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
  109. }
  110. let struct_name = args.struct_name.as_ref().unwrap();
  111. match server_macro_impl(
  112. quote::quote!(#args),
  113. mapped_body,
  114. trait_obj_wrapper,
  115. None,
  116. Some(server_fn_path.clone()),
  117. ) {
  118. Err(e) => e.to_compile_error().into(),
  119. Ok(tokens) => quote::quote! {
  120. #tokens
  121. #[cfg(feature = "ssr")]
  122. #server_fn_path::inventory::submit! {
  123. ::dioxus_fullstack::prelude::ServerFnMiddleware {
  124. prefix: #struct_name::PREFIX,
  125. url: #struct_name::URL,
  126. middleware: || vec![
  127. #(
  128. std::sync::Arc::new(#middlewares),
  129. ),*
  130. ]
  131. }
  132. }
  133. }
  134. .to_token_stream()
  135. .into(),
  136. }
  137. }
  138. #[derive(Debug)]
  139. struct Middleware {
  140. expr: syn::Expr,
  141. }
  142. impl ToTokens for Middleware {
  143. fn to_tokens(&self, tokens: &mut TokenStream2) {
  144. let expr = &self.expr;
  145. tokens.extend(quote::quote! {
  146. #expr
  147. });
  148. }
  149. }
  150. impl Parse for Middleware {
  151. fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
  152. let arg: syn::Expr = input.parse()?;
  153. Ok(Middleware { expr: arg })
  154. }
  155. }
  156. struct ServerFnArgs {
  157. struct_name: Option<Ident>,
  158. _comma: Option<Token![,]>,
  159. prefix: Option<Literal>,
  160. _comma2: Option<Token![,]>,
  161. encoding: Option<Literal>,
  162. _comma3: Option<Token![,]>,
  163. fn_path: Option<Literal>,
  164. }
  165. impl ToTokens for ServerFnArgs {
  166. fn to_tokens(&self, tokens: &mut TokenStream2) {
  167. let struct_name = self.struct_name.as_ref().map(|s| quote::quote! { #s, });
  168. let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
  169. let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
  170. let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
  171. tokens.extend(quote::quote! {
  172. #struct_name
  173. #prefix
  174. #encoding
  175. #fn_path
  176. })
  177. }
  178. }
  179. impl Parse for ServerFnArgs {
  180. fn parse(input: ParseStream) -> syn::Result<Self> {
  181. let struct_name = input.parse()?;
  182. let _comma = input.parse()?;
  183. let prefix = input.parse()?;
  184. let _comma2 = input.parse()?;
  185. let encoding = input.parse()?;
  186. let _comma3 = input.parse()?;
  187. let fn_path = input.parse()?;
  188. Ok(Self {
  189. struct_name,
  190. _comma,
  191. prefix,
  192. _comma2,
  193. encoding,
  194. _comma3,
  195. fn_path,
  196. })
  197. }
  198. }