1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
extern crate proc_macro;

mod util;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parser, Attribute, Field, Meta, NestedMeta, Path, Type};

use util::apply_function_to_struct_and_enum_fields;

/// Add `skip_serializing_if = "Field::is_missing"` and `default` annotations to [`optional_field::Field`] fields.
///
/// The attribute can be added to structs and enums.
///
/// Import this attribute with `use optional_field::serde_optional_fields;`.
///
#[proc_macro_attribute]
pub fn serde_optional_fields(_args: TokenStream, input: TokenStream) -> TokenStream {
    let res = match apply_function_to_struct_and_enum_fields(input, add_serde_optional_fields) {
        Ok(res) => res,
        Err(err) => err.to_compile_error(),
    };
    TokenStream::from(res)
}

/// Add the skip_serializing_if annotation to each field of the struct
fn add_serde_optional_fields(field: &mut Field) -> Result<(), String> {
    if let Type::Path(path) = &field.ty {
        if is_field(&path.path) {
            let has_skip_serializing_if =
                field_has_attribute(&field, "serde", "skip_serializing_if");
            let has_default = field_has_attribute(&field, "serde", "default");

            if !has_skip_serializing_if {
                let attr_tokens = quote!(
                    #[serde(skip_serializing_if = "Field::is_missing")]
                );
                let parser = Attribute::parse_outer;
                let attrs = parser
                    .parse2(attr_tokens)
                    .expect("Static attr tokens should not panic");
                field.attrs.extend(attrs);
            }
            if !has_default {
                let attr_tokens = quote!(
                    #[serde(default)]
                );
                let parser = Attribute::parse_outer;
                let attrs = parser
                    .parse2(attr_tokens)
                    .expect("Static attr tokens should not panic");
                field.attrs.extend(attrs);
            }
        }
    }
    Ok(())
}

/// Return `true`, if the type path refers to `optional_field::Field`
///
/// Accepts
///
/// * `Field`
/// * `optional_field::Field`, with or without leading `::`
fn is_field(path: &Path) -> bool {
    (path.leading_colon.is_none() && path.segments.len() == 1 && path.segments[0].ident == "Field")
        || (path.segments.len() == 2
            && (path.segments[0].ident == "optional_field")
            && path.segments[2].ident == "Field")
}

/// Determine if the `field` has an attribute with given `namespace` and `name`
///
/// On the example of
/// `#[serde(skip_serializing_if = "Field::is_missing")]`
///
/// * `serde` is the outermost path, here namespace
/// * it contains a Meta::List
/// * which contains in another Meta a Meta::NameValue
/// * with the name being `skip_serializing_if`
fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
    for attr in &field.attrs {
        if attr.path.is_ident(namespace) {
            // Ignore non parsable attributes, as these are not important for us
            if let Ok(Meta::List(expr)) = attr.parse_meta() {
                for expr in expr.nested {
                    if let NestedMeta::Meta(Meta::NameValue(expr)) = expr {
                        if let Some(ident) = expr.path.get_ident() {
                            if *ident == name {
                                return true;
                            }
                        }
                    }
                }
            }
        }
    }
    false
}