1#![warn(
12 anonymous_parameters,
13 bare_trait_objects,
14 elided_lifetimes_in_paths,
15 missing_copy_implementations,
16 rust_2018_idioms,
17 trivial_casts,
18 trivial_numeric_casts,
19 unreachable_pub,
20 unsafe_code,
21 unused_extern_crates,
22 unused_import_braces
23)]
24#![warn(
26 clippy::all,
27 clippy::cargo,
28 clippy::dbg_macro,
29 clippy::float_cmp_const,
30 clippy::get_unwrap,
31 clippy::mem_forget,
32 clippy::nursery,
33 clippy::pedantic,
34 clippy::todo,
35 clippy::unwrap_used,
36 clippy::uninlined_format_args
37)]
38#![allow(
40 clippy::default_trait_access,
41 clippy::doc_markdown,
42 clippy::if_not_else,
43 clippy::module_name_repetitions,
44 clippy::multiple_crate_versions,
45 clippy::must_use_candidate,
46 clippy::needless_pass_by_value,
47 clippy::needless_ifs,
48 clippy::use_self,
49 clippy::cargo_common_metadata,
50 clippy::missing_errors_doc,
51 clippy::enum_glob_use,
52 clippy::struct_excessive_bools,
53 clippy::missing_const_for_fn,
54 clippy::redundant_pub_crate,
55 clippy::result_large_err,
56 clippy::future_not_send,
57 clippy::option_if_let_else,
58 clippy::from_over_into,
59 clippy::manual_inspect
60)]
61#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
63
64#[allow(unused_extern_crates)]
65extern crate proc_macro;
66
67use proc_macro2::{Ident, TokenStream};
68use proc_macro_crate::{crate_name, FoundCrate};
69use quote::{format_ident, quote};
70#[cfg(feature = "slog")]
71use syn::parse_quote;
72use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Result};
73
74mod container;
75mod derive_enum;
76mod derive_struct;
77mod generics;
78mod strategy;
79mod transform;
80mod types;
81use container::{parse_container_options, ContainerOptions};
82use derive_enum::derive_enum;
83use derive_struct::derive_struct;
84use generics::{add_classified_value_bounds, add_container_bounds, add_debug_bounds};
85
86#[proc_macro_derive(Sensitive, attributes(sensitive))]
118pub fn derive_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
119 let input = parse_macro_input!(input as DeriveInput);
120 match expand(input) {
121 Ok(tokens) => tokens.into(),
122 Err(err) => err.into_compile_error().into(),
123 }
124}
125
126fn crate_root() -> proc_macro2::TokenStream {
131 match crate_name("redaction") {
132 Ok(FoundCrate::Itself) => quote! { crate },
133 Ok(FoundCrate::Name(name)) => {
134 let ident = format_ident!("{}", name);
135 quote! { ::#ident }
136 }
137 Err(_) => quote! { ::redaction },
138 }
139}
140
141fn crate_path(item: &str) -> proc_macro2::TokenStream {
142 let root = crate_root();
143 let item_ident = syn::parse_str::<syn::Path>(item).expect("redaction crate path should parse");
144 quote! { #root::#item_ident }
145}
146
147struct DeriveOutput {
148 redaction_body: TokenStream,
149 used_generics: Vec<Ident>,
150 classified_generics: Vec<Ident>,
151 debug_redacted_body: TokenStream,
152 debug_redacted_generics: Vec<Ident>,
153 debug_unredacted_body: TokenStream,
154 debug_unredacted_generics: Vec<Ident>,
155}
156
157#[allow(clippy::too_many_lines)]
158fn expand(input: DeriveInput) -> Result<TokenStream> {
159 let DeriveInput {
160 ident,
161 generics,
162 data,
163 attrs,
164 ..
165 } = input;
166
167 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
168
169 let crate_root = crate_root();
170
171 let derive_output = match &data {
172 Data::Struct(data) => {
173 let output = derive_struct(&ident, data.clone(), &generics)?;
174 DeriveOutput {
175 redaction_body: output.redaction_body,
176 used_generics: output.used_generics,
177 classified_generics: output.classified_generics,
178 debug_redacted_body: output.debug_redacted_body,
179 debug_redacted_generics: output.debug_redacted_generics,
180 debug_unredacted_body: output.debug_unredacted_body,
181 debug_unredacted_generics: output.debug_unredacted_generics,
182 }
183 }
184 Data::Enum(data) => {
185 let output = derive_enum(&ident, data.clone(), &generics)?;
186 DeriveOutput {
187 redaction_body: output.redaction_body,
188 used_generics: output.used_generics,
189 classified_generics: output.classified_generics,
190 debug_redacted_body: output.debug_redacted_body,
191 debug_redacted_generics: output.debug_redacted_generics,
192 debug_unredacted_body: output.debug_unredacted_body,
193 debug_unredacted_generics: output.debug_unredacted_generics,
194 }
195 }
196 Data::Union(u) => {
197 return Err(syn::Error::new(
198 u.union_token.span(),
199 "`Sensitive` cannot be derived for unions",
200 ));
201 }
202 };
203
204 let classify_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
205 let classify_generics =
206 add_classified_value_bounds(classify_generics, &derive_output.classified_generics);
207 let (impl_generics, ty_generics, where_clause) = classify_generics.split_for_impl();
208 let debug_redacted_generics =
209 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
210 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
211 debug_redacted_generics.split_for_impl();
212 let debug_unredacted_generics =
213 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
214 let (
215 debug_unredacted_impl_generics,
216 debug_unredacted_ty_generics,
217 debug_unredacted_where_clause,
218 ) = debug_unredacted_generics.split_for_impl();
219 let redaction_body = &derive_output.redaction_body;
220 let debug_redacted_body = &derive_output.debug_redacted_body;
221 let debug_unredacted_body = &derive_output.debug_unredacted_body;
222 let debug_impl = if skip_debug {
223 quote! {}
224 } else {
225 quote! {
226 #[cfg(any(test, feature = "testing"))]
227 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
228 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
229 #debug_unredacted_body
230 }
231 }
232
233 #[cfg(not(any(test, feature = "testing")))]
234 #[allow(unused_variables)]
235 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
236 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
237 #debug_redacted_body
238 }
239 }
240 }
241 };
242
243 #[cfg(feature = "slog")]
247 let slog_impl = {
248 let mut slog_generics = generics;
249 let slog_where_clause = slog_generics.make_where_clause();
250 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
251 slog_where_clause
252 .predicates
253 .push(parse_quote!(#self_ty: ::core::clone::Clone));
254 slog_where_clause
257 .predicates
258 .push(parse_quote!(#self_ty: ::serde::Serialize));
259 slog_where_clause
260 .predicates
261 .push(parse_quote!(#self_ty: #crate_root::slog::IntoRedactedJson));
262 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
263 slog_generics.split_for_impl();
264 quote! {
265 impl #slog_impl_generics ::slog::Value for #ident #slog_ty_generics #slog_where_clause {
266 fn serialize(
267 &self,
268 _record: &::slog::Record<'_>,
269 key: ::slog::Key,
270 serializer: &mut dyn ::slog::Serializer,
271 ) -> ::slog::Result {
272 let redacted = #crate_root::slog::IntoRedactedJson::into_redacted_json(self.clone());
273 ::slog::Value::serialize(&redacted, _record, key, serializer)
274 }
275 }
276 }
277 };
278
279 #[cfg(not(feature = "slog"))]
280 let slog_impl = quote! {};
281
282 let trait_impl = quote! {
283 impl #impl_generics #crate_root::SensitiveType for #ident #ty_generics #where_clause {
284 fn redact_with<M: #crate_root::RedactionMapper>(self, mapper: &M) -> Self {
285 use #crate_root::SensitiveType as _;
286 #redaction_body
287 }
288 }
289
290 #debug_impl
291
292 #slog_impl
293
294 };
297 Ok(trait_impl)
298}