optional_fields_serde_macro/
lib.rs

1extern crate proc_macro;
2
3mod util;
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse::Parser, Attribute, Field, Meta, NestedMeta, Path, Type};
8
9use util::apply_function_to_struct_and_enum_fields;
10
11/// Add `skip_serializing_if = "Field::is_missing"` and `default` annotations to [`optional_field::Field`] fields.
12///
13/// The attribute can be added to structs and enums.
14///
15/// Import this attribute with `use optional_field::serde_optional_fields;`.
16///
17#[proc_macro_attribute]
18pub fn serde_optional_fields(_args: TokenStream, input: TokenStream) -> TokenStream {
19    let res = match apply_function_to_struct_and_enum_fields(input, add_serde_optional_fields) {
20        Ok(res) => res,
21        Err(err) => err.to_compile_error(),
22    };
23    TokenStream::from(res)
24}
25
26/// Add the skip_serializing_if annotation to each field of the struct
27fn add_serde_optional_fields(field: &mut Field) -> Result<(), String> {
28    if let Type::Path(path) = &field.ty {
29        if is_field(&path.path) {
30            let has_skip_serializing_if =
31                field_has_attribute(field, "serde", "skip_serializing_if");
32            let has_default = field_has_attribute(field, "serde", "default");
33
34            if !has_skip_serializing_if {
35                let attr_tokens = quote!(
36                    #[serde(skip_serializing_if = "optional_field::Field::is_missing")]
37                );
38                let parser = Attribute::parse_outer;
39                let attrs = parser
40                    .parse2(attr_tokens)
41                    .expect("Static attr tokens should not panic");
42                field.attrs.extend(attrs);
43            }
44            if !has_default {
45                let attr_tokens = quote!(
46                    #[serde(default)]
47                );
48                let parser = Attribute::parse_outer;
49                let attrs = parser
50                    .parse2(attr_tokens)
51                    .expect("Static attr tokens should not panic");
52                field.attrs.extend(attrs);
53            }
54        }
55    }
56    Ok(())
57}
58
59/// Return `true`, if the type path refers to `optional_field::Field`
60///
61/// Accepts
62///
63/// * `Field`
64/// * `optional_field::Field`, with or without leading `::`
65fn is_field(path: &Path) -> bool {
66    (path.leading_colon.is_none() && path.segments.len() == 1 && path.segments[0].ident == "Field")
67        || (path.segments.len() == 2
68            && (path.segments[0].ident == "optional_field")
69            && path.segments[1].ident == "Field")
70}
71
72/// Determine if the `field` has an attribute with given `namespace` and `name`
73///
74/// On the example of
75/// `#[serde(skip_serializing_if = "Field::is_missing")]`
76///
77/// * `serde` is the outermost path, here namespace
78/// * it contains a Meta::List
79/// * which contains in another Meta a Meta::NameValue
80/// * with the name being `skip_serializing_if`
81fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
82    for attr in &field.attrs {
83        if attr.path.is_ident(namespace) {
84            // Ignore non parsable attributes, as these are not important for us
85            if let Ok(Meta::List(expr)) = attr.parse_meta() {
86                for expr in expr.nested {
87                    if let NestedMeta::Meta(Meta::NameValue(expr)) = expr {
88                        if let Some(ident) = expr.path.get_ident() {
89                            if *ident == name {
90                                return true;
91                            }
92                        }
93                    }
94                }
95            }
96        }
97    }
98    false
99}