redacted_debug/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::{TokenStream, Ident};
4use quote::{quote, quote_spanned};
5use syn::spanned::Spanned;
6use syn::{parse_macro_input, Data, DeriveInput, Fields, Index};
7
8#[proc_macro_derive(RedactedDebug, attributes(redacted))]
9pub fn derive_redacted_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11
12    let name = input.ident;
13
14    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
15
16    let body = redacted_debug_body(&name, &input.data);
17
18    #[cfg(feature = "std")]
19    let (trayt, formatter) = (quote!(::std::fmt::Debug), quote!(::std::fmt::Formatter));
20
21    #[cfg(not(feature = "std"))]
22    let (trayt, formatter) = (quote!(::core::fmt::Debug), quote!(::core::fmt::Formatter));
23
24
25    let expanded = quote! {
26        impl #impl_generics #trayt for #name #ty_generics #where_clause {
27            fn fmt(&self, f: &mut #formatter) -> ::std::fmt::Result {
28                    #body
29            }
30        }
31    };
32
33    // Hand the output tokens back to the compiler.
34    proc_macro::TokenStream::from(expanded)
35}
36
37fn is_redacted(attrs: &Vec<syn::Attribute>) -> bool {
38   for attr in attrs {
39       if attr.path.is_ident("redacted") {
40           return true
41       }
42   }
43   false
44}
45
46fn redacted_debug_body(name: &Ident, data: &Data) -> TokenStream {
47    match *data {
48        Data::Struct(ref data) => {
49            match data.fields {
50                Fields::Named(ref fields) => {
51                    let recurse = fields.named.iter().map(|f| {
52                        let name = &f.ident;
53                        if is_redacted(&f.attrs) {
54                            quote_spanned! {f.span()=>
55                                .field(stringify!(#name), &"\"...\"")
56                            }
57                        } else {
58                            quote_spanned! {f.span()=>
59                                .field(stringify!(#name), &self.#name)
60                            }
61                        }
62                    });
63                    quote! {
64                        f.debug_struct(stringify!(#name))
65                        #(#recurse)*
66                        .finish()
67                    }
68                }
69                Fields::Unnamed(ref fields) => {
70                    let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
71                        let index = Index::from(i);
72                        if is_redacted(&f.attrs) {
73                            quote_spanned! {f.span()=>
74                                .field(&"\"...\"")
75                            }
76                        } else {
77                            quote_spanned! {f.span()=>
78                                .field(&self.#index)
79                            }
80                        }
81                    });
82                    quote! {
83                        f.debug_tuple(stringify!(#name))
84                        #(#recurse)*
85                        .finish()
86                    }
87                }
88                Fields::Unit => {
89                    quote! {
90                        write!(f, stringify!(#name))
91                    }
92                }
93            }
94        }
95        Data::Enum(ref data) => {
96            let variants = data.variants.iter().map(|v| {
97                let name = &v.ident;
98                match v.fields {
99                Fields::Named(ref fields) => {
100                    let recurse = fields.named.iter().map(|f| {
101                        let name = &f.ident;
102                        if is_redacted(&f.attrs) {
103                            quote_spanned! {f.span()=>
104                                .field(stringify!(#name), &"\"...\"")
105                            }
106                        } else {
107                            quote_spanned! {f.span()=>
108                                .field(stringify!(#name), &self.#name)
109                            }
110                        }
111                    });
112                    quote! {
113                        f.debug_struct(stringify!(#name))
114                        #(#recurse)*
115                        .finish()
116                    }
117                }
118                Fields::Unnamed(ref fields) => {
119                    let recurse = fields.unnamed.iter().enumerate().map(|(i, f)| {
120                        let index = Index::from(i);
121                        if is_redacted(&f.attrs) {
122                            quote_spanned! {f.span()=>
123                                .field(&"\"...\"")
124                            }
125                        } else {
126                            quote_spanned! {f.span()=>
127                                .field(&self.#index)
128                            }
129                        }
130                    });
131                    quote! {
132                        #name => {
133                            f.debug_tuple(stringify!(#name))
134                                #(#recurse)*
135                                .finish()
136                        }
137                    }
138                }
139                Fields::Unit => {
140                    quote! {
141                        #name => {
142                            write!(f, stringify!(#name))
143                        }
144                    }
145                }
146            }
147            });
148            quote! {
149                match self {
150                    #(#variants,)*
151                }
152            }
153        }
154        Data::Union(_) => panic!("this trait cannot be derived for unions"),
155    }
156}