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;
#[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)
}
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(())
}
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")
}
fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
for attr in &field.attrs {
if attr.path.is_ident(namespace) {
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
}