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}