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 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}