1#![warn(
13 anonymous_parameters,
14 bare_trait_objects,
15 elided_lifetimes_in_paths,
16 missing_copy_implementations,
17 rust_2018_idioms,
18 trivial_casts,
19 trivial_numeric_casts,
20 unreachable_pub,
21 unsafe_code,
22 unused_extern_crates,
23 unused_import_braces
24)]
25#![warn(
27 clippy::all,
28 clippy::cargo,
29 clippy::dbg_macro,
30 clippy::float_cmp_const,
31 clippy::get_unwrap,
32 clippy::mem_forget,
33 clippy::nursery,
34 clippy::pedantic,
35 clippy::todo,
36 clippy::unwrap_used,
37 clippy::uninlined_format_args
38)]
39#![allow(
41 clippy::default_trait_access,
42 clippy::doc_markdown,
43 clippy::if_not_else,
44 clippy::module_name_repetitions,
45 clippy::multiple_crate_versions,
46 clippy::must_use_candidate,
47 clippy::needless_pass_by_value,
48 clippy::needless_ifs,
49 clippy::use_self,
50 clippy::cargo_common_metadata,
51 clippy::missing_errors_doc,
52 clippy::enum_glob_use,
53 clippy::struct_excessive_bools,
54 clippy::missing_const_for_fn,
55 clippy::redundant_pub_crate,
56 clippy::result_large_err,
57 clippy::future_not_send,
58 clippy::option_if_let_else,
59 clippy::from_over_into,
60 clippy::manual_inspect
61)]
62#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
64
65#[allow(unused_extern_crates)]
66extern crate proc_macro;
67
68use proc_macro_crate::{FoundCrate, crate_name};
69#[cfg(feature = "slog")]
70use proc_macro2::Span;
71use proc_macro2::{Ident, TokenStream};
72use quote::{format_ident, quote};
73#[cfg(feature = "slog")]
74use syn::parse_quote;
75use syn::{Data, DeriveInput, Result, parse_macro_input, spanned::Spanned};
76
77mod container;
78mod derive_enum;
79mod derive_struct;
80mod generics;
81mod redacted_display;
82mod strategy;
83mod transform;
84mod types;
85use container::{ContainerOptions, parse_container_options};
86use derive_enum::derive_enum;
87use derive_struct::derive_struct;
88use generics::{
89 add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
90 add_policy_applicable_ref_bounds, add_redacted_display_bounds,
91};
92use redacted_display::derive_redacted_display;
93
94#[proc_macro_derive(Sensitive, attributes(sensitive))]
128pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
129 let input = parse_macro_input!(input as DeriveInput);
130 match expand(input, SlogMode::RedactedJson) {
131 Ok(tokens) => tokens.into(),
132 Err(err) => err.into_compile_error().into(),
133 }
134}
135
136#[proc_macro_derive(SensitiveData, attributes(sensitive))]
137pub fn derive_sensitive_data(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
138 derive_sensitive_container(input)
139}
140
141#[proc_macro_derive(NotSensitive)]
146pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
147 let input = parse_macro_input!(input as DeriveInput);
148 let ident = input.ident;
149 let generics = input.generics;
150 let attrs = input.attrs;
151 let data = input.data;
152
153 let mut sensitive_attr_spans = Vec::new();
154 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
155 sensitive_attr_spans.push(attr.span());
156 }
157
158 match &data {
159 Data::Struct(data) => {
160 for field in &data.fields {
161 if field
162 .attrs
163 .iter()
164 .any(|attr| attr.path().is_ident("sensitive"))
165 {
166 sensitive_attr_spans.push(field.span());
167 }
168 }
169 }
170 Data::Enum(data) => {
171 for variant in &data.variants {
172 for field in &variant.fields {
173 if field
174 .attrs
175 .iter()
176 .any(|attr| attr.path().is_ident("sensitive"))
177 {
178 sensitive_attr_spans.push(field.span());
179 }
180 }
181 }
182 }
183 Data::Union(data) => {
184 return syn::Error::new(
185 data.union_token.span(),
186 "`NotSensitive` cannot be derived for unions",
187 )
188 .into_compile_error()
189 .into();
190 }
191 }
192
193 if let Some(span) = sensitive_attr_spans.first() {
194 return syn::Error::new(
195 *span,
196 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
197 )
198 .into_compile_error()
199 .into();
200 }
201 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
202 let crate_root = crate_root();
203
204 let tokens = quote! {
205 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
206 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
207 self
208 }
209 }
210 };
211 tokens.into()
212}
213
214#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
231pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
232 let input = parse_macro_input!(input as DeriveInput);
233 match expand(input, SlogMode::RedactedDisplay) {
234 Ok(tokens) => tokens.into(),
235 Err(err) => err.into_compile_error().into(),
236 }
237}
238
239fn crate_root() -> proc_macro2::TokenStream {
244 match crate_name("redactable") {
245 Ok(FoundCrate::Itself) => quote! { crate },
246 Ok(FoundCrate::Name(name)) => {
247 let ident = format_ident!("{}", name);
248 quote! { ::#ident }
249 }
250 Err(_) => quote! { ::redactable },
251 }
252}
253
254#[cfg(feature = "slog")]
260fn slog_crate() -> Result<proc_macro2::TokenStream> {
261 match crate_name("slog") {
262 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
263 Ok(FoundCrate::Name(name)) => {
264 let ident = format_ident!("{}", name);
265 Ok(quote! { ::#ident })
266 }
267 Err(_) => {
268 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
269 syn::Error::new(
270 Span::call_site(),
271 "slog support is enabled, but no top-level `slog` crate was found. \
272Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
273`slog` as a direct dependency.",
274 )
275 })?;
276 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
277 syn::Error::new(
278 Span::call_site(),
279 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
280 )
281 })?;
282 Ok(quote! { #path })
283 }
284 }
285}
286
287fn crate_path(item: &str) -> proc_macro2::TokenStream {
288 let root = crate_root();
289 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
290 quote! { #root::#item_ident }
291}
292
293struct DeriveOutput {
294 redaction_body: TokenStream,
295 used_generics: Vec<Ident>,
296 policy_applicable_generics: Vec<Ident>,
297 debug_redacted_body: TokenStream,
298 debug_redacted_generics: Vec<Ident>,
299 debug_unredacted_body: TokenStream,
300 debug_unredacted_generics: Vec<Ident>,
301 redacted_display_body: Option<TokenStream>,
302 redacted_display_generics: Vec<Ident>,
303 redacted_display_debug_generics: Vec<Ident>,
304 redacted_display_policy_ref_generics: Vec<Ident>,
305 redacted_display_nested_generics: Vec<Ident>,
306}
307
308enum SlogMode {
309 RedactedJson,
310 RedactedDisplay,
311}
312
313#[allow(clippy::too_many_lines)]
314fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
315 let DeriveInput {
316 ident,
317 generics,
318 data,
319 attrs,
320 ..
321 } = input;
322
323 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
324
325 let crate_root = crate_root();
326
327 if matches!(slog_mode, SlogMode::RedactedDisplay) {
328 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
329 let redacted_display_generics =
330 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
331 let redacted_display_generics = add_debug_bounds(
332 redacted_display_generics,
333 &redacted_display_output.debug_generics,
334 );
335 let redacted_display_generics = add_policy_applicable_ref_bounds(
336 redacted_display_generics,
337 &redacted_display_output.policy_ref_generics,
338 );
339 let redacted_display_generics = add_redacted_display_bounds(
340 redacted_display_generics,
341 &redacted_display_output.nested_generics,
342 );
343 let (display_impl_generics, display_ty_generics, display_where_clause) =
344 redacted_display_generics.split_for_impl();
345 let redacted_display_body = redacted_display_output.body;
346 let redacted_display_impl = quote! {
347 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
348 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
349 #redacted_display_body
350 }
351 }
352 };
353
354 #[cfg(feature = "slog")]
357 let slog_impl = {
358 let slog_crate = slog_crate()?;
359 let mut slog_generics = generics;
360 let (_, ty_generics, _) = slog_generics.split_for_impl();
362 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
363 slog_generics
364 .make_where_clause()
365 .predicates
366 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
367 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
368 slog_generics.split_for_impl();
369 quote! {
370 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
371 fn serialize(
372 &self,
373 _record: &#slog_crate::Record<'_>,
374 key: #slog_crate::Key,
375 serializer: &mut dyn #slog_crate::Serializer,
376 ) -> #slog_crate::Result {
377 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
378 serializer.emit_arguments(key, &format_args!("{}", redacted))
379 }
380 }
381
382 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
383 }
384 };
385
386 #[cfg(not(feature = "slog"))]
387 let slog_impl = quote! {};
388
389 #[cfg(feature = "tracing")]
390 let tracing_impl = {
391 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
392 redacted_display_generics.split_for_impl();
393 quote! {
394 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
395 }
396 };
397
398 #[cfg(not(feature = "tracing"))]
399 let tracing_impl = quote! {};
400
401 return Ok(quote! {
402 #redacted_display_impl
403 #slog_impl
404 #tracing_impl
405 });
406 }
407
408 let redacted_display_output = if matches!(slog_mode, SlogMode::RedactedDisplay) {
409 Some(derive_redacted_display(&ident, &data, &attrs, &generics)?)
410 } else {
411 None
412 };
413
414 let derive_output = match &data {
415 Data::Struct(data) => {
416 let output = derive_struct(&ident, data.clone(), &generics)?;
417 DeriveOutput {
418 redaction_body: output.redaction_body,
419 used_generics: output.used_generics,
420 policy_applicable_generics: output.policy_applicable_generics,
421 debug_redacted_body: output.debug_redacted_body,
422 debug_redacted_generics: output.debug_redacted_generics,
423 debug_unredacted_body: output.debug_unredacted_body,
424 debug_unredacted_generics: output.debug_unredacted_generics,
425 redacted_display_body: redacted_display_output
426 .as_ref()
427 .map(|output| output.body.clone()),
428 redacted_display_generics: redacted_display_output
429 .as_ref()
430 .map(|output| output.display_generics.clone())
431 .unwrap_or_default(),
432 redacted_display_debug_generics: redacted_display_output
433 .as_ref()
434 .map(|output| output.debug_generics.clone())
435 .unwrap_or_default(),
436 redacted_display_policy_ref_generics: redacted_display_output
437 .as_ref()
438 .map(|output| output.policy_ref_generics.clone())
439 .unwrap_or_default(),
440 redacted_display_nested_generics: redacted_display_output
441 .as_ref()
442 .map(|output| output.nested_generics.clone())
443 .unwrap_or_default(),
444 }
445 }
446 Data::Enum(data) => {
447 let output = derive_enum(&ident, data.clone(), &generics)?;
448 DeriveOutput {
449 redaction_body: output.redaction_body,
450 used_generics: output.used_generics,
451 policy_applicable_generics: output.policy_applicable_generics,
452 debug_redacted_body: output.debug_redacted_body,
453 debug_redacted_generics: output.debug_redacted_generics,
454 debug_unredacted_body: output.debug_unredacted_body,
455 debug_unredacted_generics: output.debug_unredacted_generics,
456 redacted_display_body: redacted_display_output
457 .as_ref()
458 .map(|output| output.body.clone()),
459 redacted_display_generics: redacted_display_output
460 .as_ref()
461 .map(|output| output.display_generics.clone())
462 .unwrap_or_default(),
463 redacted_display_debug_generics: redacted_display_output
464 .as_ref()
465 .map(|output| output.debug_generics.clone())
466 .unwrap_or_default(),
467 redacted_display_policy_ref_generics: redacted_display_output
468 .as_ref()
469 .map(|output| output.policy_ref_generics.clone())
470 .unwrap_or_default(),
471 redacted_display_nested_generics: redacted_display_output
472 .as_ref()
473 .map(|output| output.nested_generics.clone())
474 .unwrap_or_default(),
475 }
476 }
477 Data::Union(u) => {
478 return Err(syn::Error::new(
479 u.union_token.span(),
480 "`Sensitive` cannot be derived for unions",
481 ));
482 }
483 };
484
485 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
486 let policy_generics =
487 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
488 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
489 let debug_redacted_generics =
490 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
491 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
492 debug_redacted_generics.split_for_impl();
493 let debug_unredacted_generics =
494 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
495 let (
496 debug_unredacted_impl_generics,
497 debug_unredacted_ty_generics,
498 debug_unredacted_where_clause,
499 ) = debug_unredacted_generics.split_for_impl();
500 let redaction_body = &derive_output.redaction_body;
501 let debug_redacted_body = &derive_output.debug_redacted_body;
502 let debug_unredacted_body = &derive_output.debug_unredacted_body;
503 let debug_impl = if skip_debug {
504 quote! {}
505 } else {
506 quote! {
507 #[cfg(any(test, feature = "testing"))]
508 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
509 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
510 #debug_unredacted_body
511 }
512 }
513
514 #[cfg(not(any(test, feature = "testing")))]
515 #[allow(unused_variables)]
516 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
517 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
518 #debug_redacted_body
519 }
520 }
521 }
522 };
523
524 let redacted_display_body = derive_output.redacted_display_body.as_ref();
525 let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
526 let redacted_display_generics =
527 add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
528 let redacted_display_generics = add_debug_bounds(
529 redacted_display_generics,
530 &derive_output.redacted_display_debug_generics,
531 );
532 let redacted_display_generics = add_policy_applicable_ref_bounds(
533 redacted_display_generics,
534 &derive_output.redacted_display_policy_ref_generics,
535 );
536 let redacted_display_generics = add_redacted_display_bounds(
537 redacted_display_generics,
538 &derive_output.redacted_display_nested_generics,
539 );
540 let (display_impl_generics, display_ty_generics, display_where_clause) =
541 redacted_display_generics.split_for_impl();
542 let redacted_display_body = redacted_display_body
543 .cloned()
544 .unwrap_or_else(TokenStream::new);
545 quote! {
546 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
547 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
548 #redacted_display_body
549 }
550 }
551 }
552 } else {
553 quote! {}
554 };
555
556 #[cfg(feature = "slog")]
559 let slog_impl = {
560 let slog_crate = slog_crate()?;
561 let mut slog_generics = generics;
562 let slog_where_clause = slog_generics.make_where_clause();
563 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
564 match slog_mode {
565 SlogMode::RedactedJson => {
566 slog_where_clause
567 .predicates
568 .push(parse_quote!(#self_ty: ::core::clone::Clone));
569 slog_where_clause
572 .predicates
573 .push(parse_quote!(#self_ty: ::serde::Serialize));
574 slog_where_clause
575 .predicates
576 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
577 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
578 slog_generics.split_for_impl();
579 quote! {
580 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
581 fn serialize(
582 &self,
583 _record: &#slog_crate::Record<'_>,
584 key: #slog_crate::Key,
585 serializer: &mut dyn #slog_crate::Serializer,
586 ) -> #slog_crate::Result {
587 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
588 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
589 }
590 }
591
592 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
593 }
594 }
595 SlogMode::RedactedDisplay => {
596 slog_where_clause
597 .predicates
598 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
599 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
600 slog_generics.split_for_impl();
601 quote! {
602 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
603 fn serialize(
604 &self,
605 _record: &#slog_crate::Record<'_>,
606 key: #slog_crate::Key,
607 serializer: &mut dyn #slog_crate::Serializer,
608 ) -> #slog_crate::Result {
609 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
610 serializer.emit_arguments(key, &format_args!("{}", redacted))
611 }
612 }
613
614 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
615 }
616 }
617 }
618 };
619
620 #[cfg(not(feature = "slog"))]
621 let slog_impl = quote! {};
622
623 #[cfg(feature = "tracing")]
624 let tracing_impl = quote! {
625 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
626 };
627
628 #[cfg(not(feature = "tracing"))]
629 let tracing_impl = quote! {};
630
631 let trait_impl = quote! {
632 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
633 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
634 use #crate_root::RedactableContainer as _;
635 #redaction_body
636 }
637 }
638
639 #debug_impl
640
641 #redacted_display_impl
642
643 #slog_impl
644
645 #tracing_impl
646
647 };
650 Ok(trait_impl)
651}