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
67#[cfg(feature = "slog")]
68use proc_macro2::Span;
69use proc_macro2::{Ident, TokenStream};
70use proc_macro_crate::{crate_name, FoundCrate};
71use quote::{format_ident, quote};
72#[cfg(feature = "slog")]
73use syn::parse_quote;
74use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Result};
75
76mod container;
77mod derive_enum;
78mod derive_struct;
79mod generics;
80mod strategy;
81mod transform;
82mod types;
83use container::{parse_container_options, ContainerOptions};
84use derive_enum::derive_enum;
85use derive_struct::derive_struct;
86use generics::{add_classified_value_bounds, add_container_bounds, add_debug_bounds};
87
88#[proc_macro_derive(Sensitive, attributes(sensitive))]
122pub fn derive_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
123 let input = parse_macro_input!(input as DeriveInput);
124 match expand(input) {
125 Ok(tokens) => tokens.into(),
126 Err(err) => err.into_compile_error().into(),
127 }
128}
129
130fn crate_root() -> proc_macro2::TokenStream {
135 match crate_name("redaction") {
136 Ok(FoundCrate::Itself) => quote! { crate },
137 Ok(FoundCrate::Name(name)) => {
138 let ident = format_ident!("{}", name);
139 quote! { ::#ident }
140 }
141 Err(_) => quote! { ::redaction },
142 }
143}
144
145#[cfg(feature = "slog")]
151fn slog_crate() -> Result<proc_macro2::TokenStream> {
152 match crate_name("slog") {
153 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
154 Ok(FoundCrate::Name(name)) => {
155 let ident = format_ident!("{}", name);
156 Ok(quote! { ::#ident })
157 }
158 Err(_) => {
159 let env_value = std::env::var("REDACTION_SLOG_CRATE").map_err(|_| {
160 syn::Error::new(
161 Span::call_site(),
162 "slog support is enabled, but no top-level `slog` crate was found. \
163Set the REDACTION_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
164`slog` as a direct dependency.",
165 )
166 })?;
167 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
168 syn::Error::new(
169 Span::call_site(),
170 format!("REDACTION_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
171 )
172 })?;
173 Ok(quote! { #path })
174 }
175 }
176}
177
178fn crate_path(item: &str) -> proc_macro2::TokenStream {
179 let root = crate_root();
180 let item_ident = syn::parse_str::<syn::Path>(item).expect("redaction crate path should parse");
181 quote! { #root::#item_ident }
182}
183
184struct DeriveOutput {
185 redaction_body: TokenStream,
186 used_generics: Vec<Ident>,
187 classified_generics: Vec<Ident>,
188 debug_redacted_body: TokenStream,
189 debug_redacted_generics: Vec<Ident>,
190 debug_unredacted_body: TokenStream,
191 debug_unredacted_generics: Vec<Ident>,
192}
193
194#[allow(clippy::too_many_lines)]
195fn expand(input: DeriveInput) -> Result<TokenStream> {
196 let DeriveInput {
197 ident,
198 generics,
199 data,
200 attrs,
201 ..
202 } = input;
203
204 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
205
206 let crate_root = crate_root();
207
208 let derive_output = match &data {
209 Data::Struct(data) => {
210 let output = derive_struct(&ident, data.clone(), &generics)?;
211 DeriveOutput {
212 redaction_body: output.redaction_body,
213 used_generics: output.used_generics,
214 classified_generics: output.classified_generics,
215 debug_redacted_body: output.debug_redacted_body,
216 debug_redacted_generics: output.debug_redacted_generics,
217 debug_unredacted_body: output.debug_unredacted_body,
218 debug_unredacted_generics: output.debug_unredacted_generics,
219 }
220 }
221 Data::Enum(data) => {
222 let output = derive_enum(&ident, data.clone(), &generics)?;
223 DeriveOutput {
224 redaction_body: output.redaction_body,
225 used_generics: output.used_generics,
226 classified_generics: output.classified_generics,
227 debug_redacted_body: output.debug_redacted_body,
228 debug_redacted_generics: output.debug_redacted_generics,
229 debug_unredacted_body: output.debug_unredacted_body,
230 debug_unredacted_generics: output.debug_unredacted_generics,
231 }
232 }
233 Data::Union(u) => {
234 return Err(syn::Error::new(
235 u.union_token.span(),
236 "`Sensitive` cannot be derived for unions",
237 ));
238 }
239 };
240
241 let classify_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
242 let classify_generics =
243 add_classified_value_bounds(classify_generics, &derive_output.classified_generics);
244 let (impl_generics, ty_generics, where_clause) = classify_generics.split_for_impl();
245 let debug_redacted_generics =
246 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
247 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
248 debug_redacted_generics.split_for_impl();
249 let debug_unredacted_generics =
250 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
251 let (
252 debug_unredacted_impl_generics,
253 debug_unredacted_ty_generics,
254 debug_unredacted_where_clause,
255 ) = debug_unredacted_generics.split_for_impl();
256 let redaction_body = &derive_output.redaction_body;
257 let debug_redacted_body = &derive_output.debug_redacted_body;
258 let debug_unredacted_body = &derive_output.debug_unredacted_body;
259 let debug_impl = if skip_debug {
260 quote! {}
261 } else {
262 quote! {
263 #[cfg(any(test, feature = "testing"))]
264 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
265 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
266 #debug_unredacted_body
267 }
268 }
269
270 #[cfg(not(any(test, feature = "testing")))]
271 #[allow(unused_variables)]
272 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
273 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
274 #debug_redacted_body
275 }
276 }
277 }
278 };
279
280 #[cfg(feature = "slog")]
283 let slog_impl = {
284 let slog_crate = slog_crate()?;
285 let mut slog_generics = generics;
286 let slog_where_clause = slog_generics.make_where_clause();
287 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
288 slog_where_clause
289 .predicates
290 .push(parse_quote!(#self_ty: ::core::clone::Clone));
291 slog_where_clause
294 .predicates
295 .push(parse_quote!(#self_ty: ::serde::Serialize));
296 slog_where_clause
297 .predicates
298 .push(parse_quote!(#self_ty: #crate_root::slog::IntoRedactedJson));
299 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
300 slog_generics.split_for_impl();
301 quote! {
302 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
303 fn serialize(
304 &self,
305 _record: &#slog_crate::Record<'_>,
306 key: #slog_crate::Key,
307 serializer: &mut dyn #slog_crate::Serializer,
308 ) -> #slog_crate::Result {
309 let redacted = #crate_root::slog::IntoRedactedJson::into_redacted_json(self.clone());
310 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
311 }
312 }
313 }
314 };
315
316 #[cfg(not(feature = "slog"))]
317 let slog_impl = quote! {};
318
319 let trait_impl = quote! {
320 impl #impl_generics #crate_root::SensitiveType for #ident #ty_generics #where_clause {
321 fn redact_with<M: #crate_root::RedactionMapper>(self, mapper: &M) -> Self {
322 use #crate_root::SensitiveType as _;
323 #redaction_body
324 }
325 }
326
327 #debug_impl
328
329 #slog_impl
330
331 };
334 Ok(trait_impl)
335}