Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use #[server(default)] to pass use default values for a field #2231

Merged
merged 2 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,16 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// Whatever encoding is provided to `input` should implement `IntoReq` and `FromReq`. Whatever encoding is provided
/// to `output` should implement `IntoRes` and `FromRes`.
///
/// ## Default Values for Parameters
///
/// Individual function parameters can be annotated with `#[server(default)]`, which will pass
/// through `#[serde(default)]`. This is useful for the empty values of arguments with some
/// encodings. The URL encoding, for example, omits a field entirely if it is an empty `Vec<_>`,
/// but this causes a deserialization error: the correct solution is to add `#[server(default)]`.
/// ```rust,ignore
/// pub async fn with_default_value(#[server(default)] values: Vec<u32>) /* etc. */
/// ```
///
/// ## Important Notes
/// - **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
Expand Down
97 changes: 48 additions & 49 deletions server_fn_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,54 @@ pub fn server_macro_impl(
}
});

let fields = body
.inputs
.iter_mut()
.map(|f| {
let typed_arg = match f {
FnArg::Receiver(_) => {
return Err(syn::Error::new(
f.span(),
"cannot use receiver types in server function macro",
))
}
FnArg::Typed(t) => t,
};

// strip `mut`, which is allowed in fn args but not in struct fields
if let Pat::Ident(ident) = &mut *typed_arg.pat {
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;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("default") && meta.input.is_empty() {
default = true;
Ok(())
} else {
Err(meta.error(
"Unrecognized #[server] attribute, expected \
#[server(default)]",
))
}
})?;
}
typed_arg.attrs = other_attrs;
if default {
Ok(quote! { #[serde(default)] pub #typed_arg })
} else {
Ok(quote! { pub #typed_arg })
}
})
.collect::<Result<Vec<_>>>()?;

let dummy = body.to_dummy_output();
let dummy_name = body.to_dummy_ident();
let args = syn::parse::<ServerFnArgs>(args.into())?;
Expand Down Expand Up @@ -130,60 +178,11 @@ pub fn server_macro_impl(
};

// build struct for type
let mut body = body;
let fn_name = &body.ident;
let fn_name_as_str = body.ident.to_string();
let vis = body.vis;
let attrs = body.attrs;

let fields = body
.inputs
.iter_mut()
.map(|f| {
let typed_arg = match f {
FnArg::Receiver(_) => {
return Err(syn::Error::new(
f.span(),
"cannot use receiver types in server function macro",
))
}
FnArg::Typed(t) => t,
};

// strip `mut`, which is allowed in fn args but not in struct fields
if let Pat::Ident(ident) = &mut *typed_arg.pat {
ident.mutability = None;
}

// allow #[server(default)] on fields — TODO is this documented?
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;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("default") && meta.input.is_empty() {
default = true;
Ok(())
} else {
Err(meta.error(
"Unrecognized #[server] attribute, expected \
#[server(default)]",
))
}
})?;
}
typed_arg.attrs = other_attrs;
if default {
Ok(quote! { #[serde(default)] pub #typed_arg })
} else {
Ok(quote! { pub #typed_arg })
}
})
.collect::<Result<Vec<_>>>()?;

let fn_args = body
.inputs
.iter()
Expand Down
Loading