Skip to main content

adsabs_macro/
lib.rs

1use quote::quote;
2use syn::{AttributeArgs, ItemStruct, NestedMeta};
3
4/// Processes a struct to convert the fields to `Option`s
5///
6/// For now, this will always convert _all_ field types to `Option`, but the
7/// goal is to someday add filtering for skipping some fields. The usage is
8/// straightforward: just decorate your `struct` with `#[make_optional]`. For
9/// example, the following
10///
11/// ```
12/// use adsabs_macro::make_optional;
13///
14/// #[make_optional]
15/// struct ExampleStruct {
16///     id: usize,
17///     name: String,
18/// }
19/// ```
20///
21/// will be re-written to something like
22///
23/// ```
24/// struct ExampleStruct {
25///     id: Option<usize>,
26///     name: Option<String>,
27/// }
28/// ```
29#[proc_macro_attribute]
30pub fn make_optional(
31    args: proc_macro::TokenStream,
32    input: proc_macro::TokenStream,
33) -> proc_macro::TokenStream {
34    let args = syn::parse_macro_input!(args as AttributeArgs);
35    let mut input = syn::parse_macro_input!(input as ItemStruct);
36    impl_make_optional(&args, &mut input).into()
37}
38
39fn impl_make_optional(_args: &[NestedMeta], obj: &mut ItemStruct) -> proc_macro2::TokenStream {
40    match obj.fields {
41        syn::Fields::Named(ref mut fields) => fields.named.iter_mut().for_each(update_field),
42        syn::Fields::Unnamed(ref mut fields) => fields.unnamed.iter_mut().for_each(update_field),
43        syn::Fields::Unit => {}
44    }
45    quote! {
46        #obj
47    }
48}
49
50fn update_field(field: &mut syn::Field) {
51    // Add skip_serializing_if for serde
52    let attr = syn::parse_quote!(
53        #[serde(skip_serializing_if = "Option::is_none")]
54    );
55    field.attrs.push(attr);
56
57    // Update the field to be an Option
58    let orig_ty = &field.ty;
59    field.ty = syn::Type::Verbatim(quote!(Option<#orig_ty>));
60}