1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Field, ItemStruct};
4
5pub fn deserialize_default_on_error(input: TokenStream) -> TokenStream {
6    let Ok(input) = syn::parse::<ItemStruct>(input) else {
7        panic!("macro can only be applied to struct definitions");
8    };
9
10    assert!(
11        input.generics.params.is_empty(),
12        "macro does not support structs with generic parameters"
13    );
14
15    let ItemStruct { attrs: struct_attrs, vis: struct_vis, ident: struct_ident, fields, .. } =
16        &input;
17
18    let mut deserialize_fn_definitions = Vec::new();
19    let mut new_fields = Vec::new();
20
21    for field in fields {
22        let Some(field_ident) = &field.ident else {
23            panic!("macro does not support structs with unnamed fields");
24        };
25
26        let Field { attrs: field_attrs, vis: field_vis, ty: field_ty, .. } = &field;
27
28        let deserialize_fn_ident =
29            format_ident!("__deserialize_{struct_ident}_{field_ident}_default_on_error");
30
31        let cfg_attrs: Vec<_> =
32            field_attrs.iter().filter(|attr| attr.path().is_ident("cfg")).collect();
33        deserialize_fn_definitions.push(quote! {
34            #(#cfg_attrs)*
35            fn #deserialize_fn_ident<'de, D>(deserializer: D) -> ::std::result::Result<#field_ty, D::Error>
36            where
37                D: ::serde::Deserializer<'de>,
38            {
39                let value = <#field_ty as ::serde::Deserialize<'de>>::deserialize(deserializer)
40                    .unwrap_or_else(|err| {
41                        ::log::error!("error deserializing field '{}': {err}", ::std::stringify!(#field_ident));
42                        <#struct_ident as ::std::default::Default>::default().#field_ident
43                    });
44
45                ::std::result::Result::Ok(value)
46            }
47        });
48
49        let deserialize_fn_ident_str = deserialize_fn_ident.to_string();
50        new_fields.push(quote! {
51            #(#field_attrs)*
52            #[serde(deserialize_with = #deserialize_fn_ident_str)]
53            #field_vis #field_ident: #field_ty,
54        });
55    }
56
57    quote! {
58        #(#deserialize_fn_definitions)*
59
60        #(#struct_attrs)*
61        #struct_vis struct #struct_ident {
62            #(#new_fields)*
63        }
64    }
65    .into()
66}