optional_fields_serde_macro/
lib.rs1extern 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#[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
26fn 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
59fn 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
72fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool {
82 for attr in &field.attrs {
83 if attr.path.is_ident(namespace) {
84 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}