1#![warn(
14 anonymous_parameters,
15 bare_trait_objects,
16 elided_lifetimes_in_paths,
17 missing_copy_implementations,
18 rust_2018_idioms,
19 trivial_casts,
20 trivial_numeric_casts,
21 unreachable_pub,
22 unsafe_code,
23 unused_extern_crates,
24 unused_import_braces
25)]
26#![warn(
28 clippy::all,
29 clippy::cargo,
30 clippy::dbg_macro,
31 clippy::float_cmp_const,
32 clippy::get_unwrap,
33 clippy::mem_forget,
34 clippy::nursery,
35 clippy::pedantic,
36 clippy::todo,
37 clippy::unwrap_used,
38 clippy::uninlined_format_args
39)]
40#![allow(
42 clippy::default_trait_access,
43 clippy::doc_markdown,
44 clippy::if_not_else,
45 clippy::module_name_repetitions,
46 clippy::multiple_crate_versions,
47 clippy::must_use_candidate,
48 clippy::needless_pass_by_value,
49 clippy::needless_ifs,
50 clippy::use_self,
51 clippy::cargo_common_metadata,
52 clippy::missing_errors_doc,
53 clippy::enum_glob_use,
54 clippy::struct_excessive_bools,
55 clippy::missing_const_for_fn,
56 clippy::redundant_pub_crate,
57 clippy::result_large_err,
58 clippy::future_not_send,
59 clippy::option_if_let_else,
60 clippy::from_over_into,
61 clippy::manual_inspect
62)]
63#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
65
66#[allow(unused_extern_crates)]
67extern crate proc_macro;
68
69use proc_macro_crate::{FoundCrate, crate_name};
70#[cfg(feature = "slog")]
71use proc_macro2::Span;
72use proc_macro2::{Ident, TokenStream};
73use quote::{format_ident, quote};
74#[cfg(feature = "slog")]
75use syn::parse_quote;
76use syn::{
77 Data, DataEnum, DataStruct, DeriveInput, Fields, Result, parse_macro_input, spanned::Spanned,
78};
79
80mod container;
81mod derive_enum;
82mod derive_struct;
83mod generics;
84mod redacted_display;
85mod strategy;
86mod transform;
87mod types;
88use container::{
89 ContainerOptions, NotSensitiveDisplayOptions, parse_container_options,
90 parse_not_sensitive_display_options,
91};
92use derive_enum::derive_enum;
93use derive_struct::derive_struct;
94use generics::{
95 add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
96 add_policy_applicable_ref_bounds, add_redacted_display_bounds, collect_generics_from_type,
97};
98use redacted_display::derive_redacted_display;
99
100#[proc_macro_derive(Sensitive, attributes(sensitive, not_sensitive))]
138pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
139 let input = parse_macro_input!(input as DeriveInput);
140 match expand(input, SlogMode::RedactedJson) {
141 Ok(tokens) => tokens.into(),
142 Err(err) => err.into_compile_error().into(),
143 }
144}
145
146#[proc_macro_derive(NotSensitive)]
151pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
152 let input = parse_macro_input!(input as DeriveInput);
153 let ident = input.ident;
154 let generics = input.generics;
155 let attrs = input.attrs;
156 let data = input.data;
157
158 let mut sensitive_attr_spans = Vec::new();
159 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
160 sensitive_attr_spans.push(attr.span());
161 }
162
163 match &data {
164 Data::Struct(data) => {
165 for field in &data.fields {
166 if field
167 .attrs
168 .iter()
169 .any(|attr| attr.path().is_ident("sensitive"))
170 {
171 sensitive_attr_spans.push(field.span());
172 }
173 }
174 }
175 Data::Enum(data) => {
176 for variant in &data.variants {
177 for field in &variant.fields {
178 if field
179 .attrs
180 .iter()
181 .any(|attr| attr.path().is_ident("sensitive"))
182 {
183 sensitive_attr_spans.push(field.span());
184 }
185 }
186 }
187 }
188 Data::Union(data) => {
189 return syn::Error::new(
190 data.union_token.span(),
191 "`NotSensitive` cannot be derived for unions",
192 )
193 .into_compile_error()
194 .into();
195 }
196 }
197
198 if let Some(span) = sensitive_attr_spans.first() {
199 return syn::Error::new(
200 *span,
201 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
202 )
203 .into_compile_error()
204 .into();
205 }
206 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
207 let crate_root = crate_root();
208
209 let tokens = quote! {
210 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
211 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
212 self
213 }
214 }
215 };
216 tokens.into()
217}
218
219#[proc_macro_derive(NotSensitiveDisplay, attributes(not_sensitive_display))]
260pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
261 let input = parse_macro_input!(input as DeriveInput);
262 match expand_not_sensitive_display(input) {
263 Ok(tokens) => tokens.into(),
264 Err(err) => err.into_compile_error().into(),
265 }
266}
267
268#[allow(clippy::too_many_lines)]
269fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
270 let DeriveInput {
271 ident,
272 generics,
273 data,
274 attrs,
275 ..
276 } = input;
277
278 if let Data::Union(u) = &data {
280 return Err(syn::Error::new(
281 u.union_token.span(),
282 "`NotSensitiveDisplay` cannot be derived for unions",
283 ));
284 }
285
286 let mut sensitive_attr_spans = Vec::new();
288 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
289 sensitive_attr_spans.push(attr.span());
290 }
291
292 match &data {
293 Data::Struct(data) => {
294 for field in &data.fields {
295 if field
296 .attrs
297 .iter()
298 .any(|attr| attr.path().is_ident("sensitive"))
299 {
300 sensitive_attr_spans.push(field.span());
301 }
302 }
303 }
304 Data::Enum(data) => {
305 for variant in &data.variants {
306 for field in &variant.fields {
307 if field
308 .attrs
309 .iter()
310 .any(|attr| attr.path().is_ident("sensitive"))
311 {
312 sensitive_attr_spans.push(field.span());
313 }
314 }
315 }
316 }
317 Data::Union(_) => unreachable!("unions rejected above"),
318 }
319
320 if let Some(span) = sensitive_attr_spans.first() {
321 return Err(syn::Error::new(
322 *span,
323 "`#[sensitive]` attributes are not allowed on `NotSensitiveDisplay` types",
324 ));
325 }
326
327 let mut not_sensitive_attr_spans = Vec::new();
329 match &data {
330 Data::Struct(data) => {
331 for field in &data.fields {
332 if field
333 .attrs
334 .iter()
335 .any(|attr| attr.path().is_ident("not_sensitive"))
336 {
337 not_sensitive_attr_spans.push(field.span());
338 }
339 }
340 }
341 Data::Enum(data) => {
342 for variant in &data.variants {
343 for field in &variant.fields {
344 if field
345 .attrs
346 .iter()
347 .any(|attr| attr.path().is_ident("not_sensitive"))
348 {
349 not_sensitive_attr_spans.push(field.span());
350 }
351 }
352 }
353 }
354 Data::Union(_) => unreachable!("unions rejected above"),
355 }
356
357 if let Some(span) = not_sensitive_attr_spans.first() {
358 return Err(syn::Error::new(
359 *span,
360 "`#[not_sensitive]` attributes are not needed on `NotSensitiveDisplay` types (the entire type is already non-sensitive)",
361 ));
362 }
363
364 let NotSensitiveDisplayOptions { skip_debug } = parse_not_sensitive_display_options(&attrs)?;
365
366 let crate_root = crate_root();
367
368 let (container_impl_generics, container_ty_generics, container_where_clause) =
371 generics.split_for_impl();
372 let container_impl = quote! {
373 impl #container_impl_generics #crate_root::RedactableContainer for #ident #container_ty_generics #container_where_clause {
374 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
375 self
376 }
377 }
378 };
379
380 let mut display_generics = generics.clone();
383 let display_where_clause = display_generics.make_where_clause();
384 for param in generics.type_params() {
386 let ident = ¶m.ident;
387 display_where_clause
388 .predicates
389 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
390 }
391
392 let (display_impl_generics, display_ty_generics, display_where_clause) =
393 display_generics.split_for_impl();
394
395 let redacted_display_impl = quote! {
397 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
398 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
399 ::core::fmt::Display::fmt(self, f)
400 }
401 }
402 };
403
404 let debug_impl = if skip_debug {
406 quote! {}
407 } else {
408 let mut debug_generics = generics.clone();
410 let debug_where_clause = debug_generics.make_where_clause();
411 for param in generics.type_params() {
412 let ident = ¶m.ident;
413 debug_where_clause
414 .predicates
415 .push(syn::parse_quote!(#ident: ::core::fmt::Debug));
416 }
417 let (debug_impl_generics, debug_ty_generics, debug_where_clause) =
418 debug_generics.split_for_impl();
419
420 let debug_unredacted_body = match &data {
422 Data::Struct(data) => derive_unredacted_debug_struct(&ident, data, &generics).body,
423 Data::Enum(data) => derive_unredacted_debug_enum(&ident, data, &generics).body,
424 Data::Union(_) => unreachable!(),
425 };
426
427 quote! {
428 #[cfg(any(test, feature = "testing"))]
429 impl #debug_impl_generics ::core::fmt::Debug for #ident #debug_ty_generics #debug_where_clause {
430 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
431 #debug_unredacted_body
432 }
433 }
434
435 #[cfg(not(any(test, feature = "testing")))]
436 impl #display_impl_generics ::core::fmt::Debug for #ident #display_ty_generics #display_where_clause {
437 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
438 ::core::fmt::Display::fmt(self, f)
439 }
440 }
441 }
442 };
443
444 #[cfg(feature = "slog")]
446 let slog_impl = {
447 let slog_crate = slog_crate()?;
448 let mut slog_generics = generics;
449 let (_, ty_generics, _) = slog_generics.split_for_impl();
450 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
451 slog_generics
452 .make_where_clause()
453 .predicates
454 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableDisplay));
455 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
456 slog_generics.split_for_impl();
457 quote! {
458 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
459 fn serialize(
460 &self,
461 _record: &#slog_crate::Record<'_>,
462 key: #slog_crate::Key,
463 serializer: &mut dyn #slog_crate::Serializer,
464 ) -> #slog_crate::Result {
465 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
466 serializer.emit_arguments(key, &format_args!("{}", redacted))
467 }
468 }
469
470 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
471 }
472 };
473
474 #[cfg(not(feature = "slog"))]
475 let slog_impl = quote! {};
476
477 #[cfg(feature = "tracing")]
479 let tracing_impl = {
480 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
481 display_generics.split_for_impl();
482 quote! {
483 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
484 }
485 };
486
487 #[cfg(not(feature = "tracing"))]
488 let tracing_impl = quote! {};
489
490 Ok(quote! {
491 #container_impl
492 #redacted_display_impl
493 #debug_impl
494 #slog_impl
495 #tracing_impl
496 })
497}
498
499#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
527pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
528 let input = parse_macro_input!(input as DeriveInput);
529 match expand(input, SlogMode::RedactedDisplay) {
530 Ok(tokens) => tokens.into(),
531 Err(err) => err.into_compile_error().into(),
532 }
533}
534
535fn crate_root() -> proc_macro2::TokenStream {
540 match crate_name("redactable") {
541 Ok(FoundCrate::Itself) => quote! { crate },
542 Ok(FoundCrate::Name(name)) => {
543 let ident = format_ident!("{}", name);
544 quote! { ::#ident }
545 }
546 Err(_) => quote! { ::redactable },
547 }
548}
549
550#[cfg(feature = "slog")]
556fn slog_crate() -> Result<proc_macro2::TokenStream> {
557 match crate_name("slog") {
558 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
559 Ok(FoundCrate::Name(name)) => {
560 let ident = format_ident!("{}", name);
561 Ok(quote! { ::#ident })
562 }
563 Err(_) => {
564 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
565 syn::Error::new(
566 Span::call_site(),
567 "slog support is enabled, but no top-level `slog` crate was found. \
568Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
569`slog` as a direct dependency.",
570 )
571 })?;
572 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
573 syn::Error::new(
574 Span::call_site(),
575 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
576 )
577 })?;
578 Ok(quote! { #path })
579 }
580 }
581}
582
583fn crate_path(item: &str) -> proc_macro2::TokenStream {
584 let root = crate_root();
585 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
586 quote! { #root::#item_ident }
587}
588
589struct DeriveOutput {
590 redaction_body: TokenStream,
591 used_generics: Vec<Ident>,
592 policy_applicable_generics: Vec<Ident>,
593 debug_redacted_body: TokenStream,
594 debug_redacted_generics: Vec<Ident>,
595 debug_unredacted_body: TokenStream,
596 debug_unredacted_generics: Vec<Ident>,
597 redacted_display_body: Option<TokenStream>,
598 redacted_display_generics: Vec<Ident>,
599 redacted_display_debug_generics: Vec<Ident>,
600 redacted_display_policy_ref_generics: Vec<Ident>,
601 redacted_display_nested_generics: Vec<Ident>,
602}
603
604struct DebugOutput {
605 body: TokenStream,
606 generics: Vec<Ident>,
607}
608
609enum SlogMode {
610 RedactedJson,
611 RedactedDisplay,
612}
613
614#[allow(clippy::too_many_lines, clippy::redundant_clone)]
615fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
616 let DeriveInput {
617 ident,
618 generics,
619 data,
620 attrs,
621 ..
622 } = input;
623
624 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
625
626 let crate_root = crate_root();
627
628 if matches!(slog_mode, SlogMode::RedactedDisplay) {
629 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
630 let redacted_display_generics =
631 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
632 let redacted_display_generics = add_debug_bounds(
633 redacted_display_generics,
634 &redacted_display_output.debug_generics,
635 );
636 let redacted_display_generics = add_policy_applicable_ref_bounds(
637 redacted_display_generics,
638 &redacted_display_output.policy_ref_generics,
639 );
640 let redacted_display_generics = add_redacted_display_bounds(
641 redacted_display_generics,
642 &redacted_display_output.nested_generics,
643 );
644 let (display_impl_generics, display_ty_generics, display_where_clause) =
645 redacted_display_generics.split_for_impl();
646 let redacted_display_body = redacted_display_output.body;
647 let redacted_display_impl = quote! {
648 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
649 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
650 #redacted_display_body
651 }
652 }
653 };
654 let debug_impl = if skip_debug {
655 quote! {}
656 } else {
657 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
658 let debug_unredacted_generics =
659 add_debug_bounds(generics.clone(), &debug_output.generics);
660 let (
661 debug_unredacted_impl_generics,
662 debug_unredacted_ty_generics,
663 debug_unredacted_where_clause,
664 ) = debug_unredacted_generics.split_for_impl();
665 let (
666 debug_redacted_impl_generics,
667 debug_redacted_ty_generics,
668 debug_redacted_where_clause,
669 ) = redacted_display_generics.split_for_impl();
670 let debug_unredacted_body = debug_output.body;
671 quote! {
672 #[cfg(any(test, feature = "testing"))]
673 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
674 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
675 #debug_unredacted_body
676 }
677 }
678
679 #[cfg(not(any(test, feature = "testing")))]
680 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
681 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
682 #crate_root::RedactableDisplay::fmt_redacted(self, f)
683 }
684 }
685 }
686 };
687
688 #[cfg(feature = "slog")]
691 let slog_impl = {
692 let slog_crate = slog_crate()?;
693 let mut slog_generics = generics;
694 let (_, ty_generics, _) = slog_generics.split_for_impl();
696 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
697 slog_generics
698 .make_where_clause()
699 .predicates
700 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
701 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
702 slog_generics.split_for_impl();
703 quote! {
704 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
705 fn serialize(
706 &self,
707 _record: &#slog_crate::Record<'_>,
708 key: #slog_crate::Key,
709 serializer: &mut dyn #slog_crate::Serializer,
710 ) -> #slog_crate::Result {
711 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
712 serializer.emit_arguments(key, &format_args!("{}", redacted))
713 }
714 }
715
716 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
717 }
718 };
719
720 #[cfg(not(feature = "slog"))]
721 let slog_impl = quote! {};
722
723 #[cfg(feature = "tracing")]
724 let tracing_impl = {
725 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
726 redacted_display_generics.split_for_impl();
727 quote! {
728 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
729 }
730 };
731
732 #[cfg(not(feature = "tracing"))]
733 let tracing_impl = quote! {};
734
735 return Ok(quote! {
736 #redacted_display_impl
737 #debug_impl
738 #slog_impl
739 #tracing_impl
740 });
741 }
742
743 let derive_output = match &data {
747 Data::Struct(data) => {
748 let output = derive_struct(&ident, data.clone(), &generics)?;
749 DeriveOutput {
750 redaction_body: output.redaction_body,
751 used_generics: output.used_generics,
752 policy_applicable_generics: output.policy_applicable_generics,
753 debug_redacted_body: output.debug_redacted_body,
754 debug_redacted_generics: output.debug_redacted_generics,
755 debug_unredacted_body: output.debug_unredacted_body,
756 debug_unredacted_generics: output.debug_unredacted_generics,
757 redacted_display_body: None,
758 redacted_display_generics: Vec::new(),
759 redacted_display_debug_generics: Vec::new(),
760 redacted_display_policy_ref_generics: Vec::new(),
761 redacted_display_nested_generics: Vec::new(),
762 }
763 }
764 Data::Enum(data) => {
765 let output = derive_enum(&ident, data.clone(), &generics)?;
766 DeriveOutput {
767 redaction_body: output.redaction_body,
768 used_generics: output.used_generics,
769 policy_applicable_generics: output.policy_applicable_generics,
770 debug_redacted_body: output.debug_redacted_body,
771 debug_redacted_generics: output.debug_redacted_generics,
772 debug_unredacted_body: output.debug_unredacted_body,
773 debug_unredacted_generics: output.debug_unredacted_generics,
774 redacted_display_body: None,
775 redacted_display_generics: Vec::new(),
776 redacted_display_debug_generics: Vec::new(),
777 redacted_display_policy_ref_generics: Vec::new(),
778 redacted_display_nested_generics: Vec::new(),
779 }
780 }
781 Data::Union(u) => {
782 return Err(syn::Error::new(
783 u.union_token.span(),
784 "`Sensitive` cannot be derived for unions",
785 ));
786 }
787 };
788
789 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
790 let policy_generics =
791 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
792 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
793 let debug_redacted_generics =
794 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
795 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
796 debug_redacted_generics.split_for_impl();
797 let debug_unredacted_generics =
798 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
799 let (
800 debug_unredacted_impl_generics,
801 debug_unredacted_ty_generics,
802 debug_unredacted_where_clause,
803 ) = debug_unredacted_generics.split_for_impl();
804 let redaction_body = &derive_output.redaction_body;
805 let debug_redacted_body = &derive_output.debug_redacted_body;
806 let debug_unredacted_body = &derive_output.debug_unredacted_body;
807 let debug_impl = if skip_debug {
808 quote! {}
809 } else {
810 quote! {
811 #[cfg(any(test, feature = "testing"))]
812 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
813 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
814 #debug_unredacted_body
815 }
816 }
817
818 #[cfg(not(any(test, feature = "testing")))]
819 #[allow(unused_variables)]
820 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
821 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
822 #debug_redacted_body
823 }
824 }
825 }
826 };
827
828 let redacted_display_body = derive_output.redacted_display_body.as_ref();
829 let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
830 let redacted_display_generics =
831 add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
832 let redacted_display_generics = add_debug_bounds(
833 redacted_display_generics,
834 &derive_output.redacted_display_debug_generics,
835 );
836 let redacted_display_generics = add_policy_applicable_ref_bounds(
837 redacted_display_generics,
838 &derive_output.redacted_display_policy_ref_generics,
839 );
840 let redacted_display_generics = add_redacted_display_bounds(
841 redacted_display_generics,
842 &derive_output.redacted_display_nested_generics,
843 );
844 let (display_impl_generics, display_ty_generics, display_where_clause) =
845 redacted_display_generics.split_for_impl();
846 let redacted_display_body = redacted_display_body
847 .cloned()
848 .unwrap_or_else(TokenStream::new);
849 quote! {
850 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
851 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
852 #redacted_display_body
853 }
854 }
855 }
856 } else {
857 quote! {}
858 };
859
860 #[cfg(feature = "slog")]
863 let slog_impl = {
864 let slog_crate = slog_crate()?;
865 let mut slog_generics = generics;
866 let slog_where_clause = slog_generics.make_where_clause();
867 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
868 match slog_mode {
869 SlogMode::RedactedJson => {
870 slog_where_clause
871 .predicates
872 .push(parse_quote!(#self_ty: ::core::clone::Clone));
873 slog_where_clause
876 .predicates
877 .push(parse_quote!(#self_ty: ::serde::Serialize));
878 slog_where_clause
879 .predicates
880 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
881 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
882 slog_generics.split_for_impl();
883 quote! {
884 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
885 fn serialize(
886 &self,
887 _record: &#slog_crate::Record<'_>,
888 key: #slog_crate::Key,
889 serializer: &mut dyn #slog_crate::Serializer,
890 ) -> #slog_crate::Result {
891 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
892 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
893 }
894 }
895
896 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
897 }
898 }
899 SlogMode::RedactedDisplay => {
900 slog_where_clause
901 .predicates
902 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
903 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
904 slog_generics.split_for_impl();
905 quote! {
906 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
907 fn serialize(
908 &self,
909 _record: &#slog_crate::Record<'_>,
910 key: #slog_crate::Key,
911 serializer: &mut dyn #slog_crate::Serializer,
912 ) -> #slog_crate::Result {
913 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
914 serializer.emit_arguments(key, &format_args!("{}", redacted))
915 }
916 }
917
918 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
919 }
920 }
921 }
922 };
923
924 #[cfg(not(feature = "slog"))]
925 let slog_impl = quote! {};
926
927 #[cfg(feature = "tracing")]
928 let tracing_impl = quote! {
929 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
930 };
931
932 #[cfg(not(feature = "tracing"))]
933 let tracing_impl = quote! {};
934
935 let trait_impl = quote! {
936 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
937 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
938 use #crate_root::RedactableContainer as _;
939 #redaction_body
940 }
941 }
942
943 #debug_impl
944
945 #redacted_display_impl
946
947 #slog_impl
948
949 #tracing_impl
950
951 };
954 Ok(trait_impl)
955}
956
957fn derive_unredacted_debug(
958 name: &Ident,
959 data: &Data,
960 generics: &syn::Generics,
961) -> Result<DebugOutput> {
962 match data {
963 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
964 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
965 Data::Union(u) => Err(syn::Error::new(
966 u.union_token.span(),
967 "`SensitiveDisplay` cannot be derived for unions",
968 )),
969 }
970}
971
972fn derive_unredacted_debug_struct(
973 name: &Ident,
974 data: &DataStruct,
975 generics: &syn::Generics,
976) -> DebugOutput {
977 let mut debug_generics = Vec::new();
978 match &data.fields {
979 Fields::Named(fields) => {
980 let mut bindings = Vec::new();
981 let mut debug_fields = Vec::new();
982 for field in &fields.named {
983 let ident = field
984 .ident
985 .clone()
986 .expect("named field should have identifier");
987 bindings.push(ident.clone());
988 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
989 debug_fields.push(quote! {
990 debug.field(stringify!(#ident), #ident);
991 });
992 }
993 DebugOutput {
994 body: quote! {
995 match self {
996 Self { #(#bindings),* } => {
997 let mut debug = f.debug_struct(stringify!(#name));
998 #(#debug_fields)*
999 debug.finish()
1000 }
1001 }
1002 },
1003 generics: debug_generics,
1004 }
1005 }
1006 Fields::Unnamed(fields) => {
1007 let mut bindings = Vec::new();
1008 let mut debug_fields = Vec::new();
1009 for (index, field) in fields.unnamed.iter().enumerate() {
1010 let ident = format_ident!("field_{index}");
1011 bindings.push(ident.clone());
1012 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1013 debug_fields.push(quote! {
1014 debug.field(#ident);
1015 });
1016 }
1017 DebugOutput {
1018 body: quote! {
1019 match self {
1020 Self ( #(#bindings),* ) => {
1021 let mut debug = f.debug_tuple(stringify!(#name));
1022 #(#debug_fields)*
1023 debug.finish()
1024 }
1025 }
1026 },
1027 generics: debug_generics,
1028 }
1029 }
1030 Fields::Unit => DebugOutput {
1031 body: quote! {
1032 f.write_str(stringify!(#name))
1033 },
1034 generics: debug_generics,
1035 },
1036 }
1037}
1038
1039fn derive_unredacted_debug_enum(
1040 name: &Ident,
1041 data: &DataEnum,
1042 generics: &syn::Generics,
1043) -> DebugOutput {
1044 let mut debug_generics = Vec::new();
1045 let mut debug_arms = Vec::new();
1046 for variant in &data.variants {
1047 let variant_ident = &variant.ident;
1048 match &variant.fields {
1049 Fields::Unit => {
1050 debug_arms.push(quote! {
1051 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
1052 });
1053 }
1054 Fields::Named(fields) => {
1055 let mut bindings = Vec::new();
1056 let mut debug_fields = Vec::new();
1057 for field in &fields.named {
1058 let ident = field
1059 .ident
1060 .clone()
1061 .expect("named field should have identifier");
1062 bindings.push(ident.clone());
1063 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1064 debug_fields.push(quote! {
1065 debug.field(stringify!(#ident), #ident);
1066 });
1067 }
1068 debug_arms.push(quote! {
1069 #name::#variant_ident { #(#bindings),* } => {
1070 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
1071 #(#debug_fields)*
1072 debug.finish()
1073 }
1074 });
1075 }
1076 Fields::Unnamed(fields) => {
1077 let mut bindings = Vec::new();
1078 let mut debug_fields = Vec::new();
1079 for (index, field) in fields.unnamed.iter().enumerate() {
1080 let ident = format_ident!("field_{index}");
1081 bindings.push(ident.clone());
1082 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1083 debug_fields.push(quote! {
1084 debug.field(#ident);
1085 });
1086 }
1087 debug_arms.push(quote! {
1088 #name::#variant_ident ( #(#bindings),* ) => {
1089 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1090 #(#debug_fields)*
1091 debug.finish()
1092 }
1093 });
1094 }
1095 }
1096 }
1097 DebugOutput {
1098 body: quote! {
1099 match self {
1100 #(#debug_arms),*
1101 }
1102 },
1103 generics: debug_generics,
1104 }
1105}