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::{ContainerOptions, parse_container_options};
89use derive_enum::derive_enum;
90use derive_struct::derive_struct;
91use generics::{
92 add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
93 add_policy_applicable_ref_bounds, add_redacted_display_bounds, collect_generics_from_type,
94};
95use redacted_display::derive_redacted_display;
96
97#[proc_macro_derive(Sensitive, attributes(sensitive, not_sensitive))]
135pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
136 let input = parse_macro_input!(input as DeriveInput);
137 match expand(input, SlogMode::RedactedJson) {
138 Ok(tokens) => tokens.into(),
139 Err(err) => err.into_compile_error().into(),
140 }
141}
142
143#[proc_macro_derive(NotSensitive, attributes(not_sensitive))]
169pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
170 let input = parse_macro_input!(input as DeriveInput);
171 match expand_not_sensitive(input) {
172 Ok(tokens) => tokens.into(),
173 Err(err) => err.into_compile_error().into(),
174 }
175}
176
177#[allow(clippy::too_many_lines)]
178fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
179 let DeriveInput {
180 ident,
181 generics,
182 data,
183 attrs,
184 ..
185 } = input;
186
187 if let Data::Union(u) = &data {
189 return Err(syn::Error::new(
190 u.union_token.span(),
191 "`NotSensitive` cannot be derived for unions",
192 ));
193 }
194
195 for attr in &attrs {
199 if attr.path().is_ident("sensitive") {
200 return Err(syn::Error::new(
201 attr.span(),
202 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
203 ));
204 }
205 if attr.path().is_ident("not_sensitive") {
206 return Err(syn::Error::new(
207 attr.span(),
208 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
209 ));
210 }
211 }
212
213 match &data {
214 Data::Struct(data) => {
215 for field in &data.fields {
216 for attr in &field.attrs {
217 if attr.path().is_ident("sensitive") {
218 return Err(syn::Error::new(
219 attr.span(),
220 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
221 ));
222 }
223 if attr.path().is_ident("not_sensitive") {
224 return Err(syn::Error::new(
225 attr.span(),
226 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
227 ));
228 }
229 }
230 }
231 }
232 Data::Enum(data) => {
233 for variant in &data.variants {
234 for field in &variant.fields {
235 for attr in &field.attrs {
236 if attr.path().is_ident("sensitive") {
237 return Err(syn::Error::new(
238 attr.span(),
239 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
240 ));
241 }
242 if attr.path().is_ident("not_sensitive") {
243 return Err(syn::Error::new(
244 attr.span(),
245 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
246 ));
247 }
248 }
249 }
250 }
251 }
252 Data::Union(_) => unreachable!("unions rejected above"),
253 }
254
255 let crate_root = crate_root();
256
257 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
259 let container_impl = quote! {
260 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
261 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
262 self
263 }
264 }
265 };
266
267 #[cfg(feature = "slog")]
269 let slog_impl = {
270 let slog_crate = slog_crate()?;
271 let mut slog_generics = generics.clone();
272 let (_, ty_generics, _) = slog_generics.split_for_impl();
273 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
274 slog_generics
275 .make_where_clause()
276 .predicates
277 .push(parse_quote!(#self_ty: ::serde::Serialize));
278 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
279 slog_generics.split_for_impl();
280 quote! {
281 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
282 fn serialize(
283 &self,
284 _record: &#slog_crate::Record<'_>,
285 key: #slog_crate::Key,
286 serializer: &mut dyn #slog_crate::Serializer,
287 ) -> #slog_crate::Result {
288 #crate_root::slog::__slog_serialize_not_sensitive(self, _record, key, serializer)
289 }
290 }
291
292 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
293 }
294 };
295
296 #[cfg(not(feature = "slog"))]
297 let slog_impl = quote! {};
298
299 #[cfg(feature = "tracing")]
301 let tracing_impl = {
302 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
303 generics.split_for_impl();
304 quote! {
305 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
306 }
307 };
308
309 #[cfg(not(feature = "tracing"))]
310 let tracing_impl = quote! {};
311
312 Ok(quote! {
313 #container_impl
314 #slog_impl
315 #tracing_impl
316 })
317}
318
319#[proc_macro_derive(NotSensitiveDisplay)]
363pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
364 let input = parse_macro_input!(input as DeriveInput);
365 match expand_not_sensitive_display(input) {
366 Ok(tokens) => tokens.into(),
367 Err(err) => err.into_compile_error().into(),
368 }
369}
370
371#[allow(clippy::too_many_lines)]
372fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
373 let DeriveInput {
374 ident,
375 generics,
376 data,
377 attrs,
378 ..
379 } = input;
380
381 if let Data::Union(u) = &data {
383 return Err(syn::Error::new(
384 u.union_token.span(),
385 "`NotSensitiveDisplay` cannot be derived for unions",
386 ));
387 }
388
389 let mut sensitive_attr_spans = Vec::new();
391 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
392 sensitive_attr_spans.push(attr.span());
393 }
394
395 match &data {
396 Data::Struct(data) => {
397 for field in &data.fields {
398 if field
399 .attrs
400 .iter()
401 .any(|attr| attr.path().is_ident("sensitive"))
402 {
403 sensitive_attr_spans.push(field.span());
404 }
405 }
406 }
407 Data::Enum(data) => {
408 for variant in &data.variants {
409 for field in &variant.fields {
410 if field
411 .attrs
412 .iter()
413 .any(|attr| attr.path().is_ident("sensitive"))
414 {
415 sensitive_attr_spans.push(field.span());
416 }
417 }
418 }
419 }
420 Data::Union(_) => unreachable!("unions rejected above"),
421 }
422
423 if let Some(span) = sensitive_attr_spans.first() {
424 return Err(syn::Error::new(
425 *span,
426 "`#[sensitive]` attributes are not allowed on `NotSensitiveDisplay` types",
427 ));
428 }
429
430 let mut not_sensitive_attr_spans = Vec::new();
432 match &data {
433 Data::Struct(data) => {
434 for field in &data.fields {
435 if field
436 .attrs
437 .iter()
438 .any(|attr| attr.path().is_ident("not_sensitive"))
439 {
440 not_sensitive_attr_spans.push(field.span());
441 }
442 }
443 }
444 Data::Enum(data) => {
445 for variant in &data.variants {
446 for field in &variant.fields {
447 if field
448 .attrs
449 .iter()
450 .any(|attr| attr.path().is_ident("not_sensitive"))
451 {
452 not_sensitive_attr_spans.push(field.span());
453 }
454 }
455 }
456 }
457 Data::Union(_) => unreachable!("unions rejected above"),
458 }
459
460 if let Some(span) = not_sensitive_attr_spans.first() {
461 return Err(syn::Error::new(
462 *span,
463 "`#[not_sensitive]` attributes are not needed on `NotSensitiveDisplay` types (the entire type is already non-sensitive)",
464 ));
465 }
466
467 let crate_root = crate_root();
468
469 let (container_impl_generics, container_ty_generics, container_where_clause) =
472 generics.split_for_impl();
473 let container_impl = quote! {
474 impl #container_impl_generics #crate_root::RedactableContainer for #ident #container_ty_generics #container_where_clause {
475 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
476 self
477 }
478 }
479 };
480
481 let mut display_generics = generics.clone();
484 let display_where_clause = display_generics.make_where_clause();
485 for param in generics.type_params() {
487 let ident = ¶m.ident;
488 display_where_clause
489 .predicates
490 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
491 }
492
493 let (display_impl_generics, display_ty_generics, display_where_clause) =
494 display_generics.split_for_impl();
495
496 let redacted_display_impl = quote! {
498 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
499 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
500 ::core::fmt::Display::fmt(self, f)
501 }
502 }
503 };
504
505 #[cfg(feature = "slog")]
507 let slog_impl = {
508 let slog_crate = slog_crate()?;
509 let mut slog_generics = generics;
510 let (_, ty_generics, _) = slog_generics.split_for_impl();
511 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
512 slog_generics
513 .make_where_clause()
514 .predicates
515 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableDisplay));
516 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
517 slog_generics.split_for_impl();
518 quote! {
519 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
520 fn serialize(
521 &self,
522 _record: &#slog_crate::Record<'_>,
523 key: #slog_crate::Key,
524 serializer: &mut dyn #slog_crate::Serializer,
525 ) -> #slog_crate::Result {
526 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
527 serializer.emit_arguments(key, &format_args!("{}", redacted))
528 }
529 }
530
531 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
532 }
533 };
534
535 #[cfg(not(feature = "slog"))]
536 let slog_impl = quote! {};
537
538 #[cfg(feature = "tracing")]
540 let tracing_impl = {
541 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
542 display_generics.split_for_impl();
543 quote! {
544 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
545 }
546 };
547
548 #[cfg(not(feature = "tracing"))]
549 let tracing_impl = quote! {};
550
551 Ok(quote! {
552 #container_impl
553 #redacted_display_impl
554 #slog_impl
555 #tracing_impl
556 })
557}
558
559#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
587pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
588 let input = parse_macro_input!(input as DeriveInput);
589 match expand(input, SlogMode::RedactedDisplay) {
590 Ok(tokens) => tokens.into(),
591 Err(err) => err.into_compile_error().into(),
592 }
593}
594
595fn crate_root() -> proc_macro2::TokenStream {
600 match crate_name("redactable") {
601 Ok(FoundCrate::Itself) => quote! { crate },
602 Ok(FoundCrate::Name(name)) => {
603 let ident = format_ident!("{}", name);
604 quote! { ::#ident }
605 }
606 Err(_) => quote! { ::redactable },
607 }
608}
609
610#[cfg(feature = "slog")]
616fn slog_crate() -> Result<proc_macro2::TokenStream> {
617 match crate_name("slog") {
618 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
619 Ok(FoundCrate::Name(name)) => {
620 let ident = format_ident!("{}", name);
621 Ok(quote! { ::#ident })
622 }
623 Err(_) => {
624 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
625 syn::Error::new(
626 Span::call_site(),
627 "slog support is enabled, but no top-level `slog` crate was found. \
628Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
629`slog` as a direct dependency.",
630 )
631 })?;
632 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
633 syn::Error::new(
634 Span::call_site(),
635 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
636 )
637 })?;
638 Ok(quote! { #path })
639 }
640 }
641}
642
643fn crate_path(item: &str) -> proc_macro2::TokenStream {
644 let root = crate_root();
645 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
646 quote! { #root::#item_ident }
647}
648
649struct DeriveOutput {
650 redaction_body: TokenStream,
651 used_generics: Vec<Ident>,
652 policy_applicable_generics: Vec<Ident>,
653 debug_redacted_body: TokenStream,
654 debug_redacted_generics: Vec<Ident>,
655 debug_unredacted_body: TokenStream,
656 debug_unredacted_generics: Vec<Ident>,
657 redacted_display_body: Option<TokenStream>,
658 redacted_display_generics: Vec<Ident>,
659 redacted_display_debug_generics: Vec<Ident>,
660 redacted_display_policy_ref_generics: Vec<Ident>,
661 redacted_display_nested_generics: Vec<Ident>,
662}
663
664struct DebugOutput {
665 body: TokenStream,
666 generics: Vec<Ident>,
667}
668
669enum SlogMode {
670 RedactedJson,
671 RedactedDisplay,
672}
673
674#[allow(clippy::too_many_lines, clippy::redundant_clone)]
675fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
676 let DeriveInput {
677 ident,
678 generics,
679 data,
680 attrs,
681 ..
682 } = input;
683
684 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
685
686 let crate_root = crate_root();
687
688 if matches!(slog_mode, SlogMode::RedactedDisplay) {
689 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
690 let redacted_display_generics =
691 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
692 let redacted_display_generics = add_debug_bounds(
693 redacted_display_generics,
694 &redacted_display_output.debug_generics,
695 );
696 let redacted_display_generics = add_policy_applicable_ref_bounds(
697 redacted_display_generics,
698 &redacted_display_output.policy_ref_generics,
699 );
700 let redacted_display_generics = add_redacted_display_bounds(
701 redacted_display_generics,
702 &redacted_display_output.nested_generics,
703 );
704 let (display_impl_generics, display_ty_generics, display_where_clause) =
705 redacted_display_generics.split_for_impl();
706 let redacted_display_body = redacted_display_output.body;
707 let redacted_display_impl = quote! {
708 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
709 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
710 #redacted_display_body
711 }
712 }
713 };
714 let debug_impl = if skip_debug {
715 quote! {}
716 } else {
717 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
718 let debug_unredacted_generics =
719 add_debug_bounds(generics.clone(), &debug_output.generics);
720 let (
721 debug_unredacted_impl_generics,
722 debug_unredacted_ty_generics,
723 debug_unredacted_where_clause,
724 ) = debug_unredacted_generics.split_for_impl();
725 let (
726 debug_redacted_impl_generics,
727 debug_redacted_ty_generics,
728 debug_redacted_where_clause,
729 ) = redacted_display_generics.split_for_impl();
730 let debug_unredacted_body = debug_output.body;
731 quote! {
732 #[cfg(any(test, feature = "testing"))]
733 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
734 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
735 #debug_unredacted_body
736 }
737 }
738
739 #[cfg(not(any(test, feature = "testing")))]
740 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
741 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
742 #crate_root::RedactableDisplay::fmt_redacted(self, f)
743 }
744 }
745 }
746 };
747
748 #[cfg(feature = "slog")]
751 let slog_impl = {
752 let slog_crate = slog_crate()?;
753 let mut slog_generics = generics;
754 let (_, ty_generics, _) = slog_generics.split_for_impl();
756 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
757 slog_generics
758 .make_where_clause()
759 .predicates
760 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
761 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
762 slog_generics.split_for_impl();
763 quote! {
764 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
765 fn serialize(
766 &self,
767 _record: &#slog_crate::Record<'_>,
768 key: #slog_crate::Key,
769 serializer: &mut dyn #slog_crate::Serializer,
770 ) -> #slog_crate::Result {
771 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
772 serializer.emit_arguments(key, &format_args!("{}", redacted))
773 }
774 }
775
776 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
777 }
778 };
779
780 #[cfg(not(feature = "slog"))]
781 let slog_impl = quote! {};
782
783 #[cfg(feature = "tracing")]
784 let tracing_impl = {
785 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
786 redacted_display_generics.split_for_impl();
787 quote! {
788 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
789 }
790 };
791
792 #[cfg(not(feature = "tracing"))]
793 let tracing_impl = quote! {};
794
795 return Ok(quote! {
796 #redacted_display_impl
797 #debug_impl
798 #slog_impl
799 #tracing_impl
800 });
801 }
802
803 let derive_output = match &data {
807 Data::Struct(data) => {
808 let output = derive_struct(&ident, data.clone(), &generics)?;
809 DeriveOutput {
810 redaction_body: output.redaction_body,
811 used_generics: output.used_generics,
812 policy_applicable_generics: output.policy_applicable_generics,
813 debug_redacted_body: output.debug_redacted_body,
814 debug_redacted_generics: output.debug_redacted_generics,
815 debug_unredacted_body: output.debug_unredacted_body,
816 debug_unredacted_generics: output.debug_unredacted_generics,
817 redacted_display_body: None,
818 redacted_display_generics: Vec::new(),
819 redacted_display_debug_generics: Vec::new(),
820 redacted_display_policy_ref_generics: Vec::new(),
821 redacted_display_nested_generics: Vec::new(),
822 }
823 }
824 Data::Enum(data) => {
825 let output = derive_enum(&ident, data.clone(), &generics)?;
826 DeriveOutput {
827 redaction_body: output.redaction_body,
828 used_generics: output.used_generics,
829 policy_applicable_generics: output.policy_applicable_generics,
830 debug_redacted_body: output.debug_redacted_body,
831 debug_redacted_generics: output.debug_redacted_generics,
832 debug_unredacted_body: output.debug_unredacted_body,
833 debug_unredacted_generics: output.debug_unredacted_generics,
834 redacted_display_body: None,
835 redacted_display_generics: Vec::new(),
836 redacted_display_debug_generics: Vec::new(),
837 redacted_display_policy_ref_generics: Vec::new(),
838 redacted_display_nested_generics: Vec::new(),
839 }
840 }
841 Data::Union(u) => {
842 return Err(syn::Error::new(
843 u.union_token.span(),
844 "`Sensitive` cannot be derived for unions",
845 ));
846 }
847 };
848
849 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
850 let policy_generics =
851 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
852 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
853 let debug_redacted_generics =
854 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
855 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
856 debug_redacted_generics.split_for_impl();
857 let debug_unredacted_generics =
858 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
859 let (
860 debug_unredacted_impl_generics,
861 debug_unredacted_ty_generics,
862 debug_unredacted_where_clause,
863 ) = debug_unredacted_generics.split_for_impl();
864 let redaction_body = &derive_output.redaction_body;
865 let debug_redacted_body = &derive_output.debug_redacted_body;
866 let debug_unredacted_body = &derive_output.debug_unredacted_body;
867 let debug_impl = if skip_debug {
868 quote! {}
869 } else {
870 quote! {
871 #[cfg(any(test, feature = "testing"))]
872 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
873 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
874 #debug_unredacted_body
875 }
876 }
877
878 #[cfg(not(any(test, feature = "testing")))]
879 #[allow(unused_variables)]
880 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
881 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
882 #debug_redacted_body
883 }
884 }
885 }
886 };
887
888 let redacted_display_body = derive_output.redacted_display_body.as_ref();
889 let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
890 let redacted_display_generics =
891 add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
892 let redacted_display_generics = add_debug_bounds(
893 redacted_display_generics,
894 &derive_output.redacted_display_debug_generics,
895 );
896 let redacted_display_generics = add_policy_applicable_ref_bounds(
897 redacted_display_generics,
898 &derive_output.redacted_display_policy_ref_generics,
899 );
900 let redacted_display_generics = add_redacted_display_bounds(
901 redacted_display_generics,
902 &derive_output.redacted_display_nested_generics,
903 );
904 let (display_impl_generics, display_ty_generics, display_where_clause) =
905 redacted_display_generics.split_for_impl();
906 let redacted_display_body = redacted_display_body
907 .cloned()
908 .unwrap_or_else(TokenStream::new);
909 quote! {
910 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
911 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
912 #redacted_display_body
913 }
914 }
915 }
916 } else {
917 quote! {}
918 };
919
920 #[cfg(feature = "slog")]
923 let slog_impl = {
924 let slog_crate = slog_crate()?;
925 let mut slog_generics = generics;
926 let slog_where_clause = slog_generics.make_where_clause();
927 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
928 match slog_mode {
929 SlogMode::RedactedJson => {
930 slog_where_clause
931 .predicates
932 .push(parse_quote!(#self_ty: ::core::clone::Clone));
933 slog_where_clause
936 .predicates
937 .push(parse_quote!(#self_ty: ::serde::Serialize));
938 slog_where_clause
939 .predicates
940 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
941 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
942 slog_generics.split_for_impl();
943 quote! {
944 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
945 fn serialize(
946 &self,
947 _record: &#slog_crate::Record<'_>,
948 key: #slog_crate::Key,
949 serializer: &mut dyn #slog_crate::Serializer,
950 ) -> #slog_crate::Result {
951 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
952 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
953 }
954 }
955
956 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
957 }
958 }
959 SlogMode::RedactedDisplay => {
960 slog_where_clause
961 .predicates
962 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
963 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
964 slog_generics.split_for_impl();
965 quote! {
966 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
967 fn serialize(
968 &self,
969 _record: &#slog_crate::Record<'_>,
970 key: #slog_crate::Key,
971 serializer: &mut dyn #slog_crate::Serializer,
972 ) -> #slog_crate::Result {
973 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
974 serializer.emit_arguments(key, &format_args!("{}", redacted))
975 }
976 }
977
978 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
979 }
980 }
981 }
982 };
983
984 #[cfg(not(feature = "slog"))]
985 let slog_impl = quote! {};
986
987 #[cfg(feature = "tracing")]
988 let tracing_impl = quote! {
989 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
990 };
991
992 #[cfg(not(feature = "tracing"))]
993 let tracing_impl = quote! {};
994
995 let trait_impl = quote! {
996 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
997 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
998 use #crate_root::RedactableContainer as _;
999 #redaction_body
1000 }
1001 }
1002
1003 #debug_impl
1004
1005 #redacted_display_impl
1006
1007 #slog_impl
1008
1009 #tracing_impl
1010
1011 };
1014 Ok(trait_impl)
1015}
1016
1017fn derive_unredacted_debug(
1018 name: &Ident,
1019 data: &Data,
1020 generics: &syn::Generics,
1021) -> Result<DebugOutput> {
1022 match data {
1023 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
1024 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
1025 Data::Union(u) => Err(syn::Error::new(
1026 u.union_token.span(),
1027 "`SensitiveDisplay` cannot be derived for unions",
1028 )),
1029 }
1030}
1031
1032fn derive_unredacted_debug_struct(
1033 name: &Ident,
1034 data: &DataStruct,
1035 generics: &syn::Generics,
1036) -> DebugOutput {
1037 let mut debug_generics = Vec::new();
1038 match &data.fields {
1039 Fields::Named(fields) => {
1040 let mut bindings = Vec::new();
1041 let mut debug_fields = Vec::new();
1042 for field in &fields.named {
1043 let ident = field
1044 .ident
1045 .clone()
1046 .expect("named field should have identifier");
1047 bindings.push(ident.clone());
1048 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1049 debug_fields.push(quote! {
1050 debug.field(stringify!(#ident), #ident);
1051 });
1052 }
1053 DebugOutput {
1054 body: quote! {
1055 match self {
1056 Self { #(#bindings),* } => {
1057 let mut debug = f.debug_struct(stringify!(#name));
1058 #(#debug_fields)*
1059 debug.finish()
1060 }
1061 }
1062 },
1063 generics: debug_generics,
1064 }
1065 }
1066 Fields::Unnamed(fields) => {
1067 let mut bindings = Vec::new();
1068 let mut debug_fields = Vec::new();
1069 for (index, field) in fields.unnamed.iter().enumerate() {
1070 let ident = format_ident!("field_{index}");
1071 bindings.push(ident.clone());
1072 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1073 debug_fields.push(quote! {
1074 debug.field(#ident);
1075 });
1076 }
1077 DebugOutput {
1078 body: quote! {
1079 match self {
1080 Self ( #(#bindings),* ) => {
1081 let mut debug = f.debug_tuple(stringify!(#name));
1082 #(#debug_fields)*
1083 debug.finish()
1084 }
1085 }
1086 },
1087 generics: debug_generics,
1088 }
1089 }
1090 Fields::Unit => DebugOutput {
1091 body: quote! {
1092 f.write_str(stringify!(#name))
1093 },
1094 generics: debug_generics,
1095 },
1096 }
1097}
1098
1099fn derive_unredacted_debug_enum(
1100 name: &Ident,
1101 data: &DataEnum,
1102 generics: &syn::Generics,
1103) -> DebugOutput {
1104 let mut debug_generics = Vec::new();
1105 let mut debug_arms = Vec::new();
1106 for variant in &data.variants {
1107 let variant_ident = &variant.ident;
1108 match &variant.fields {
1109 Fields::Unit => {
1110 debug_arms.push(quote! {
1111 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
1112 });
1113 }
1114 Fields::Named(fields) => {
1115 let mut bindings = Vec::new();
1116 let mut debug_fields = Vec::new();
1117 for field in &fields.named {
1118 let ident = field
1119 .ident
1120 .clone()
1121 .expect("named field should have identifier");
1122 bindings.push(ident.clone());
1123 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1124 debug_fields.push(quote! {
1125 debug.field(stringify!(#ident), #ident);
1126 });
1127 }
1128 debug_arms.push(quote! {
1129 #name::#variant_ident { #(#bindings),* } => {
1130 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
1131 #(#debug_fields)*
1132 debug.finish()
1133 }
1134 });
1135 }
1136 Fields::Unnamed(fields) => {
1137 let mut bindings = Vec::new();
1138 let mut debug_fields = Vec::new();
1139 for (index, field) in fields.unnamed.iter().enumerate() {
1140 let ident = format_ident!("field_{index}");
1141 bindings.push(ident.clone());
1142 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1143 debug_fields.push(quote! {
1144 debug.field(#ident);
1145 });
1146 }
1147 debug_arms.push(quote! {
1148 #name::#variant_ident ( #(#bindings),* ) => {
1149 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1150 #(#debug_fields)*
1151 debug.finish()
1152 }
1153 });
1154 }
1155 }
1156 }
1157 DebugOutput {
1158 body: quote! {
1159 match self {
1160 #(#debug_arms),*
1161 }
1162 },
1163 generics: debug_generics,
1164 }
1165}