From 9fc26e609c425004fe6c0d715699f6d79a25ca77 Mon Sep 17 00:00:00 2001 From: Rakshith Ravi Date: Sat, 31 Aug 2024 19:05:08 +0530 Subject: [PATCH] feat: allow for documentation and other attributes to fields in server fn (#2876) --- server_fn_macro/src/lib.rs | 131 ++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 23 deletions(-) diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index b4cd3a406..e419644fb 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -8,7 +8,7 @@ use convert_case::{Case, Converter}; use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, @@ -74,32 +74,117 @@ pub fn server_macro_impl( ident.mutability = None; } - // allow #[server(default)] on fields - let mut default = false; - let mut other_attrs = Vec::new(); - for attr in typed_arg.attrs.iter() { - if !attr.path().is_ident("server") { - other_attrs.push(attr.clone()); - continue; + fn rename_path( + path: Path, + from_ident: Ident, + to_ident: Ident, + ) -> Path { + if path.is_ident(&from_ident) { + Path { + leading_colon: None, + segments: Punctuated::from_iter([PathSegment { + ident: to_ident, + arguments: PathArguments::None, + }]), + } + } else { + path } - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("default") && meta.input.is_empty() { - default = true; - Ok(()) + } + + let attrs = typed_arg + .attrs + .iter() + .cloned() + .map(|attr| { + if attr.path().is_ident("server") { + // Allow the following attributes: + // - #[server(default)] + // - #[server(rename = "fieldName")] + + // Rename `server` to `serde` + let attr = Attribute { + meta: match attr.meta { + Meta::Path(path) => Meta::Path(rename_path( + path, + format_ident!("server"), + format_ident!("serde"), + )), + Meta::List(mut list) => { + list.path = rename_path( + list.path, + format_ident!("server"), + format_ident!("serde"), + ); + Meta::List(list) + } + Meta::NameValue(mut name_value) => { + name_value.path = rename_path( + name_value.path, + format_ident!("server"), + format_ident!("serde"), + ); + Meta::NameValue(name_value) + } + }, + ..attr + }; + + let args = attr.parse_args::()?; + match args { + // #[server(default)] + Meta::Path(path) if path.is_ident("default") => { + Ok(attr.clone()) + } + // #[server(flatten)] + Meta::Path(path) if path.is_ident("flatten") => { + Ok(attr.clone()) + } + // #[server(default = "value")] + Meta::NameValue(name_value) + if name_value.path.is_ident("default") => + { + Ok(attr.clone()) + } + // #[server(skip)] + Meta::Path(path) if path.is_ident("skip") => { + Ok(attr.clone()) + } + // #[server(rename = "value")] + Meta::NameValue(name_value) + if name_value.path.is_ident("rename") => + { + Ok(attr.clone()) + } + _ => Err(Error::new( + attr.span(), + "Unrecognized #[server] attribute, expected \ + #[server(default)] or #[server(rename = \ + \"fieldName\")]", + )), + } + } else if attr.path().is_ident("doc") { + // Allow #[doc = "documentation"] + Ok(attr.clone()) + } else if attr.path().is_ident("allow") { + // Allow #[allow(...)] + Ok(attr.clone()) + } else if attr.path().is_ident("deny") { + // Allow #[deny(...)] + Ok(attr.clone()) + } else if attr.path().is_ident("ignore") { + // Allow #[ignore] + Ok(attr.clone()) } else { - Err(meta.error( - "Unrecognized #[server] attribute, expected \ - #[server(default)]", + Err(Error::new( + attr.span(), + "Unrecognized attribute, expected #[server(...)]", )) } - })?; - } - typed_arg.attrs = other_attrs; - if default { - Ok(quote! { #[serde(default)] pub #typed_arg }) - } else { - Ok(quote! { pub #typed_arg }) - } + }) + .collect::>>()?; + typed_arg.attrs = vec![]; + Ok(quote! { #(#attrs ) * pub #typed_arg }) }) .collect::>>()?;