deboog_derive/
lib.rs

1use darling::{
2    ast::{Data, Fields, Style},
3    FromDeriveInput, FromField, FromMeta, FromVariant,
4};
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::{quote, ToTokens};
8use syn::{parse_macro_input, spanned::Spanned, Generics, Ident, Index};
9
10#[derive(Clone, Copy, Default, FromMeta)]
11#[darling(default, rename_all = "snake_case")]
12enum Masking {
13    #[default]
14    All,
15    Pan,
16    PanSuffix,
17    Hidden,
18}
19
20type OptionData = Data<VariantOptions, FieldOptions>;
21
22#[derive(FromDeriveInput)]
23#[darling(attributes(deboog))]
24struct Options {
25    ident: Ident,
26    generics: Generics,
27    data: OptionData,
28}
29
30#[derive(FromField)]
31#[darling(attributes(deboog))]
32struct FieldOptions {
33    ident: Option<Ident>,
34    #[darling(default)]
35    skip: bool,
36    #[darling(default)]
37    mask: Option<Masking>,
38}
39
40#[derive(FromVariant)]
41#[darling(attributes(deboog))]
42struct VariantOptions {
43    ident: Ident,
44    fields: Fields<FieldOptions>,
45}
46
47#[proc_macro_derive(Deboog, attributes(deboog))]
48pub fn derive_deboog(input: TokenStream) -> TokenStream {
49    let input = parse_macro_input!(input);
50    let opts = Options::from_derive_input(&input).unwrap();
51    let debug_impl = debug_fmt_impl(&opts.ident, &opts.generics, &opts.data);
52
53    let output = quote! { #debug_impl };
54    output.into()
55}
56
57fn debug_fmt_impl(ident: &Ident, generics: &Generics, data: &OptionData) -> TokenStream2 {
58    let debug_fmt = debug_fmt_body(ident, data);
59    quote! {
60        #[automatically_derived]
61        impl #generics std::fmt::Debug for #ident #generics {
62            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63                #debug_fmt
64            }
65        }
66    }
67}
68
69fn debug_fmt_body(ident: &Ident, data: &OptionData) -> TokenStream2 {
70    match data {
71        Data::Enum(variants) => debug_fmt_enum(variants),
72        Data::Struct(fields) => match fields.style {
73            Style::Unit => debug_fmt_unit_struct(ident),
74            Style::Struct => debug_fmt_normal_struct(ident, &fields.fields),
75            Style::Tuple => debug_fmt_tuple_struct(ident, &fields.fields),
76        },
77    }
78}
79
80fn debug_fmt_unit_struct(ident: &Ident) -> TokenStream2 {
81    let ident_str = ident.to_string();
82    quote! {
83        f.debug_struct(#ident_str).finish()
84    }
85}
86
87fn debug_fmt_normal_struct(ident: &Ident, fields: &[FieldOptions]) -> TokenStream2 {
88    let ident_str = ident.to_string();
89    let field_chunks = fields.iter().filter(|f| !f.skip).map(|f| {
90        let field = &f.ident;
91        let field_str = field.to_token_stream().to_string();
92        let field_val = transform_field(quote! { &self.#field }, f);
93        quote! { .field(#field_str, #field_val) }
94    });
95    quote! {
96        f.debug_struct(#ident_str)
97            #(#field_chunks)*
98            .finish()
99    }
100}
101
102fn debug_fmt_tuple_struct(ident: &Ident, fields: &[FieldOptions]) -> TokenStream2 {
103    let ident_str = ident.to_string();
104    let field_chunks = fields
105        .iter()
106        .enumerate()
107        .filter(|(_, f)| !f.skip)
108        .map(|(i, f)| {
109            let i = Index::from(i);
110            let field_val = transform_field(quote! { &self.#i }, f);
111            quote! { .field(#field_val) }
112        });
113    quote! {
114        f.debug_tuple(#ident_str)
115            #(#field_chunks)*
116            .finish()
117    }
118}
119
120fn debug_fmt_enum(variants: &[VariantOptions]) -> TokenStream2 {
121    let variant_chunks = variants.iter().map(|v| {
122        let var = &v.ident;
123        let var_str = var.to_string();
124
125        if v.fields.is_unit() {
126            quote! {
127                Self::#var => {
128                    f.debug_struct(#var_str).finish()
129                }
130            }
131        } else if v.fields.is_tuple() {
132            let fields = v.fields.iter().enumerate().map(|(i, f)| {
133                if f.skip {
134                    Ident::new("_", v.ident.span())
135                } else {
136                    Ident::new(&format!("f{}", i), v.ident.span())
137                }
138            });
139            let field_chunks = v
140                .fields
141                .iter()
142                .enumerate()
143                .filter(|(_, f)| !f.skip)
144                .map(|(i, f)| Ident::new(&format!("f{}", i), f.ident.span()));
145            quote! {
146                Self::#var(#(#fields),*) => {
147                    f.debug_tuple(#var_str)
148                        #(.field(#field_chunks))*
149                        .finish()
150                }
151            }
152        } else {
153            let fields = v.fields.iter().filter(|f| !f.skip).map(|f| &f.ident);
154            let variant_fields = fields.clone().map(|i| {
155                let field_str = i.to_token_stream().to_string();
156                quote! { .field(#field_str, #i) }
157            });
158            quote! {
159                Self::#var { #(#fields,)* .. } => {
160                    f.debug_struct(#var_str)
161                        #(#variant_fields)*
162                        .finish()
163                }
164            }
165        }
166    });
167    quote! {
168        match self {
169            #(#variant_chunks),*
170        }
171    }
172}
173
174fn transform_field(field: TokenStream2, opts: &FieldOptions) -> TokenStream2 {
175    match opts.mask {
176        None => field,
177        Some(mask_type) => match mask_type {
178            Masking::All => quote! { &deboog::field::Masked::All(#field) },
179            Masking::Pan => quote! { &deboog::field::Masked::Pan(#field) },
180            Masking::PanSuffix => quote! { &deboog::field::Masked::PanSuffix(#field) },
181            Masking::Hidden => quote! { &deboog::field::Masked::Hidden(#field) },
182        },
183    }
184}