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
177fn reject_sensitivity_attrs(attrs: &[syn::Attribute], data: &Data, macro_name: &str) -> Result<()> {
183 let check_attr = |attr: &syn::Attribute| -> Result<()> {
184 if attr.path().is_ident("sensitive") {
185 return Err(syn::Error::new(
186 attr.span(),
187 format!("`#[sensitive]` attributes are not allowed on `{macro_name}` types"),
188 ));
189 }
190 if attr.path().is_ident("not_sensitive") {
191 return Err(syn::Error::new(
192 attr.span(),
193 format!(
194 "`#[not_sensitive]` attributes are not needed on `{macro_name}` types (the entire type is already non-sensitive)"
195 ),
196 ));
197 }
198 Ok(())
199 };
200
201 for attr in attrs {
202 check_attr(attr)?;
203 }
204
205 match data {
206 Data::Struct(data) => {
207 for field in &data.fields {
208 for attr in &field.attrs {
209 check_attr(attr)?;
210 }
211 }
212 }
213 Data::Enum(data) => {
214 for variant in &data.variants {
215 for field in &variant.fields {
216 for attr in &field.attrs {
217 check_attr(attr)?;
218 }
219 }
220 }
221 }
222 Data::Union(_) => {}
223 }
224
225 Ok(())
226}
227
228fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
229 let DeriveInput {
230 ident,
231 generics,
232 data,
233 attrs,
234 ..
235 } = input;
236
237 if let Data::Union(u) = &data {
239 return Err(syn::Error::new(
240 u.union_token.span(),
241 "`NotSensitive` cannot be derived for unions",
242 ));
243 }
244
245 reject_sensitivity_attrs(&attrs, &data, "NotSensitive")?;
246
247 let crate_root = crate_root();
248
249 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
251 let container_impl = quote! {
252 impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
253 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
254 self
255 }
256 }
257 };
258
259 #[cfg(feature = "slog")]
261 let slog_impl = {
262 let slog_crate = slog_crate()?;
263 let mut slog_generics = generics.clone();
264 let (_, ty_generics, _) = slog_generics.split_for_impl();
265 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
266 slog_generics
267 .make_where_clause()
268 .predicates
269 .push(parse_quote!(#self_ty: ::serde::Serialize));
270 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
271 slog_generics.split_for_impl();
272 quote! {
273 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
274 fn serialize(
275 &self,
276 _record: &#slog_crate::Record<'_>,
277 key: #slog_crate::Key,
278 serializer: &mut dyn #slog_crate::Serializer,
279 ) -> #slog_crate::Result {
280 #crate_root::slog::__slog_serialize_not_sensitive(self, _record, key, serializer)
281 }
282 }
283
284 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
285 }
286 };
287
288 #[cfg(not(feature = "slog"))]
289 let slog_impl = quote! {};
290
291 #[cfg(feature = "tracing")]
293 let tracing_impl = {
294 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
295 generics.split_for_impl();
296 quote! {
297 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
298 }
299 };
300
301 #[cfg(not(feature = "tracing"))]
302 let tracing_impl = quote! {};
303
304 Ok(quote! {
305 #container_impl
306 #slog_impl
307 #tracing_impl
308 })
309}
310
311#[proc_macro_derive(NotSensitiveDisplay)]
355pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
356 let input = parse_macro_input!(input as DeriveInput);
357 match expand_not_sensitive_display(input) {
358 Ok(tokens) => tokens.into(),
359 Err(err) => err.into_compile_error().into(),
360 }
361}
362
363fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
364 let DeriveInput {
365 ident,
366 generics,
367 data,
368 attrs,
369 ..
370 } = input;
371
372 if let Data::Union(u) = &data {
374 return Err(syn::Error::new(
375 u.union_token.span(),
376 "`NotSensitiveDisplay` cannot be derived for unions",
377 ));
378 }
379
380 reject_sensitivity_attrs(&attrs, &data, "NotSensitiveDisplay")?;
381
382 let crate_root = crate_root();
383
384 let (container_impl_generics, container_ty_generics, container_where_clause) =
387 generics.split_for_impl();
388 let container_impl = quote! {
389 impl #container_impl_generics #crate_root::RedactableWithMapper for #ident #container_ty_generics #container_where_clause {
390 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
391 self
392 }
393 }
394 };
395
396 let mut display_generics = generics.clone();
399 let display_where_clause = display_generics.make_where_clause();
400 for param in generics.type_params() {
402 let ident = ¶m.ident;
403 display_where_clause
404 .predicates
405 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
406 }
407
408 let (display_impl_generics, display_ty_generics, display_where_clause) =
409 display_generics.split_for_impl();
410
411 let redacted_display_impl = quote! {
413 impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
414 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
415 ::core::fmt::Display::fmt(self, f)
416 }
417 }
418 };
419
420 #[cfg(feature = "slog")]
422 let slog_impl = {
423 let slog_crate = slog_crate()?;
424 let mut slog_generics = generics.clone();
425 let (_, ty_generics, _) = slog_generics.split_for_impl();
426 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
427 slog_generics
428 .make_where_clause()
429 .predicates
430 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
431 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
432 slog_generics.split_for_impl();
433 quote! {
434 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
435 fn serialize(
436 &self,
437 _record: &#slog_crate::Record<'_>,
438 key: #slog_crate::Key,
439 serializer: &mut dyn #slog_crate::Serializer,
440 ) -> #slog_crate::Result {
441 let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
442 serializer.emit_arguments(key, &format_args!("{}", redacted))
443 }
444 }
445
446 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
447 }
448 };
449
450 #[cfg(not(feature = "slog"))]
451 let slog_impl = quote! {};
452
453 #[cfg(feature = "tracing")]
455 let tracing_impl = {
456 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
457 generics.split_for_impl();
458 quote! {
459 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
460 }
461 };
462
463 #[cfg(not(feature = "tracing"))]
464 let tracing_impl = quote! {};
465
466 Ok(quote! {
467 #container_impl
468 #redacted_display_impl
469 #slog_impl
470 #tracing_impl
471 })
472}
473
474#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
505pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
506 let input = parse_macro_input!(input as DeriveInput);
507 match expand(input, SlogMode::RedactedDisplay) {
508 Ok(tokens) => tokens.into(),
509 Err(err) => err.into_compile_error().into(),
510 }
511}
512
513fn crate_root() -> proc_macro2::TokenStream {
518 match crate_name("redactable") {
519 Ok(FoundCrate::Itself) => quote! { crate },
520 Ok(FoundCrate::Name(name)) => {
521 let ident = format_ident!("{}", name);
522 quote! { ::#ident }
523 }
524 Err(_) => quote! { ::redactable },
525 }
526}
527
528#[cfg(feature = "slog")]
534fn slog_crate() -> Result<proc_macro2::TokenStream> {
535 match crate_name("slog") {
536 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
537 Ok(FoundCrate::Name(name)) => {
538 let ident = format_ident!("{}", name);
539 Ok(quote! { ::#ident })
540 }
541 Err(_) => {
542 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
543 syn::Error::new(
544 Span::call_site(),
545 "slog support is enabled, but no top-level `slog` crate was found. \
546Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
547`slog` as a direct dependency.",
548 )
549 })?;
550 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
551 syn::Error::new(
552 Span::call_site(),
553 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
554 )
555 })?;
556 Ok(quote! { #path })
557 }
558 }
559}
560
561fn crate_path(item: &str) -> proc_macro2::TokenStream {
562 let root = crate_root();
563 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
564 quote! { #root::#item_ident }
565}
566
567pub(crate) struct DeriveOutput {
571 pub(crate) redaction_body: TokenStream,
572 pub(crate) used_generics: Vec<Ident>,
573 pub(crate) policy_applicable_generics: Vec<Ident>,
574 pub(crate) debug_redacted_body: TokenStream,
575 pub(crate) debug_redacted_generics: Vec<Ident>,
576 pub(crate) debug_unredacted_body: TokenStream,
577 pub(crate) debug_unredacted_generics: Vec<Ident>,
578}
579
580struct DebugOutput {
581 body: TokenStream,
582 generics: Vec<Ident>,
583}
584
585enum SlogMode {
586 RedactedJson,
587 RedactedDisplay,
588}
589
590#[allow(clippy::too_many_lines)]
591fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
592 let DeriveInput {
593 ident,
594 generics,
595 data,
596 attrs,
597 ..
598 } = input;
599
600 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
601
602 let crate_root = crate_root();
603
604 if matches!(slog_mode, SlogMode::RedactedDisplay) {
605 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
606 let redacted_display_generics =
607 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
608 let redacted_display_generics = add_debug_bounds(
609 redacted_display_generics,
610 &redacted_display_output.debug_generics,
611 );
612 let redacted_display_generics = add_policy_applicable_ref_bounds(
613 redacted_display_generics,
614 &redacted_display_output.policy_ref_generics,
615 );
616 let redacted_display_generics = add_redacted_display_bounds(
617 redacted_display_generics,
618 &redacted_display_output.nested_generics,
619 );
620 let (display_impl_generics, display_ty_generics, display_where_clause) =
621 redacted_display_generics.split_for_impl();
622 let redacted_display_body = redacted_display_output.body;
623 let redacted_display_impl = quote! {
624 impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
625 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
626 #redacted_display_body
627 }
628 }
629 };
630
631 let container_derive_output = match &data {
635 Data::Struct(data) => derive_struct(&ident, data.clone(), &generics)?,
636 Data::Enum(data) => derive_enum(&ident, data.clone(), &generics)?,
637 Data::Union(u) => {
638 return Err(syn::Error::new(
639 u.union_token.span(),
640 "`SensitiveDisplay` cannot be derived for unions",
641 ));
642 }
643 };
644 let container_generics =
645 add_container_bounds(generics.clone(), &container_derive_output.used_generics);
646 let container_generics = add_policy_applicable_bounds(
647 container_generics,
648 &container_derive_output.policy_applicable_generics,
649 );
650 let (container_impl_generics, container_ty_generics, container_where_clause) =
651 container_generics.split_for_impl();
652 let container_redaction_body = &container_derive_output.redaction_body;
653 let container_impl = quote! {
654 impl #container_impl_generics #crate_root::RedactableWithMapper for #ident #container_ty_generics #container_where_clause {
655 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
656 use #crate_root::RedactableWithMapper as _;
657 #container_redaction_body
658 }
659 }
660 };
661
662 let debug_impl = if skip_debug {
663 quote! {}
664 } else {
665 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
666 let debug_unredacted_generics =
667 add_debug_bounds(generics.clone(), &debug_output.generics);
668 let (
669 debug_unredacted_impl_generics,
670 debug_unredacted_ty_generics,
671 debug_unredacted_where_clause,
672 ) = debug_unredacted_generics.split_for_impl();
673 let (
674 debug_redacted_impl_generics,
675 debug_redacted_ty_generics,
676 debug_redacted_where_clause,
677 ) = redacted_display_generics.split_for_impl();
678 let debug_unredacted_body = debug_output.body;
679 quote! {
680 #[cfg(any(test, feature = "testing"))]
681 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
682 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
683 #debug_unredacted_body
684 }
685 }
686
687 #[cfg(not(any(test, feature = "testing")))]
688 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
689 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
690 #crate_root::RedactableWithFormatter::fmt_redacted(self, f)
691 }
692 }
693 }
694 };
695
696 #[cfg(feature = "slog")]
699 let slog_impl = {
700 let slog_crate = slog_crate()?;
701 let mut slog_generics = generics;
702 let (_, ty_generics, _) = slog_generics.split_for_impl();
704 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
705 slog_generics
706 .make_where_clause()
707 .predicates
708 .push(parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
709 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
710 slog_generics.split_for_impl();
711 quote! {
712 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
713 fn serialize(
714 &self,
715 _record: &#slog_crate::Record<'_>,
716 key: #slog_crate::Key,
717 serializer: &mut dyn #slog_crate::Serializer,
718 ) -> #slog_crate::Result {
719 let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
720 serializer.emit_arguments(key, &format_args!("{}", redacted))
721 }
722 }
723
724 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
725 }
726 };
727
728 #[cfg(not(feature = "slog"))]
729 let slog_impl = quote! {};
730
731 #[cfg(feature = "tracing")]
732 let tracing_impl = {
733 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
734 redacted_display_generics.split_for_impl();
735 quote! {
736 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
737 }
738 };
739
740 #[cfg(not(feature = "tracing"))]
741 let tracing_impl = quote! {};
742
743 return Ok(quote! {
744 #redacted_display_impl
745 #container_impl
746 #debug_impl
747 #slog_impl
748 #tracing_impl
749 });
750 }
751
752 let derive_output = match &data {
756 Data::Struct(data) => derive_struct(&ident, data.clone(), &generics)?,
757 Data::Enum(data) => derive_enum(&ident, data.clone(), &generics)?,
758 Data::Union(u) => {
759 return Err(syn::Error::new(
760 u.union_token.span(),
761 "`Sensitive` cannot be derived for unions",
762 ));
763 }
764 };
765
766 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
767 let policy_generics =
768 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
769 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
770 let debug_redacted_generics =
771 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
772 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
773 debug_redacted_generics.split_for_impl();
774 let debug_unredacted_generics =
775 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
776 let (
777 debug_unredacted_impl_generics,
778 debug_unredacted_ty_generics,
779 debug_unredacted_where_clause,
780 ) = debug_unredacted_generics.split_for_impl();
781 let redaction_body = &derive_output.redaction_body;
782 let debug_redacted_body = &derive_output.debug_redacted_body;
783 let debug_unredacted_body = &derive_output.debug_unredacted_body;
784 let debug_impl = if skip_debug {
785 quote! {}
786 } else {
787 quote! {
788 #[cfg(any(test, feature = "testing"))]
789 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
790 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
791 #debug_unredacted_body
792 }
793 }
794
795 #[cfg(not(any(test, feature = "testing")))]
796 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
797 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
798 #debug_redacted_body
799 }
800 }
801 }
802 };
803
804 #[cfg(feature = "slog")]
807 let slog_impl = {
808 let slog_crate = slog_crate()?;
809 let mut slog_generics = generics;
810 let slog_where_clause = slog_generics.make_where_clause();
811 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
812 slog_where_clause
813 .predicates
814 .push(parse_quote!(#self_ty: ::core::clone::Clone));
815 slog_where_clause
818 .predicates
819 .push(parse_quote!(#self_ty: ::serde::Serialize));
820 slog_where_clause
821 .predicates
822 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
823 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
824 slog_generics.split_for_impl();
825 quote! {
826 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
827 fn serialize(
828 &self,
829 _record: &#slog_crate::Record<'_>,
830 key: #slog_crate::Key,
831 serializer: &mut dyn #slog_crate::Serializer,
832 ) -> #slog_crate::Result {
833 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
834 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
835 }
836 }
837
838 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
839 }
840 };
841
842 #[cfg(not(feature = "slog"))]
843 let slog_impl = quote! {};
844
845 #[cfg(feature = "tracing")]
846 let tracing_impl = quote! {
847 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
848 };
849
850 #[cfg(not(feature = "tracing"))]
851 let tracing_impl = quote! {};
852
853 let trait_impl = quote! {
854 impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
855 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
856 use #crate_root::RedactableWithMapper as _;
857 #redaction_body
858 }
859 }
860
861 #debug_impl
862
863 #slog_impl
864
865 #tracing_impl
866
867 };
870 Ok(trait_impl)
871}
872
873fn derive_unredacted_debug(
874 name: &Ident,
875 data: &Data,
876 generics: &syn::Generics,
877) -> Result<DebugOutput> {
878 match data {
879 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
880 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
881 Data::Union(u) => Err(syn::Error::new(
882 u.union_token.span(),
883 "`SensitiveDisplay` cannot be derived for unions",
884 )),
885 }
886}
887
888fn derive_unredacted_debug_struct(
889 name: &Ident,
890 data: &DataStruct,
891 generics: &syn::Generics,
892) -> DebugOutput {
893 let mut debug_generics = Vec::new();
894 match &data.fields {
895 Fields::Named(fields) => {
896 let mut bindings = Vec::new();
897 let mut debug_fields = Vec::new();
898 for field in &fields.named {
899 let ident = field
900 .ident
901 .clone()
902 .expect("named field should have identifier");
903 bindings.push(ident.clone());
904 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
905 debug_fields.push(quote! {
906 debug.field(stringify!(#ident), #ident);
907 });
908 }
909 DebugOutput {
910 body: quote! {
911 match self {
912 Self { #(#bindings),* } => {
913 let mut debug = f.debug_struct(stringify!(#name));
914 #(#debug_fields)*
915 debug.finish()
916 }
917 }
918 },
919 generics: debug_generics,
920 }
921 }
922 Fields::Unnamed(fields) => {
923 let mut bindings = Vec::new();
924 let mut debug_fields = Vec::new();
925 for (index, field) in fields.unnamed.iter().enumerate() {
926 let ident = format_ident!("field_{index}");
927 bindings.push(ident.clone());
928 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
929 debug_fields.push(quote! {
930 debug.field(#ident);
931 });
932 }
933 DebugOutput {
934 body: quote! {
935 match self {
936 Self ( #(#bindings),* ) => {
937 let mut debug = f.debug_tuple(stringify!(#name));
938 #(#debug_fields)*
939 debug.finish()
940 }
941 }
942 },
943 generics: debug_generics,
944 }
945 }
946 Fields::Unit => DebugOutput {
947 body: quote! {
948 f.write_str(stringify!(#name))
949 },
950 generics: debug_generics,
951 },
952 }
953}
954
955fn derive_unredacted_debug_enum(
956 name: &Ident,
957 data: &DataEnum,
958 generics: &syn::Generics,
959) -> DebugOutput {
960 let mut debug_generics = Vec::new();
961 let mut debug_arms = Vec::new();
962 for variant in &data.variants {
963 let variant_ident = &variant.ident;
964 match &variant.fields {
965 Fields::Unit => {
966 debug_arms.push(quote! {
967 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
968 });
969 }
970 Fields::Named(fields) => {
971 let mut bindings = Vec::new();
972 let mut debug_fields = Vec::new();
973 for field in &fields.named {
974 let ident = field
975 .ident
976 .clone()
977 .expect("named field should have identifier");
978 bindings.push(ident.clone());
979 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
980 debug_fields.push(quote! {
981 debug.field(stringify!(#ident), #ident);
982 });
983 }
984 debug_arms.push(quote! {
985 #name::#variant_ident { #(#bindings),* } => {
986 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
987 #(#debug_fields)*
988 debug.finish()
989 }
990 });
991 }
992 Fields::Unnamed(fields) => {
993 let mut bindings = Vec::new();
994 let mut debug_fields = Vec::new();
995 for (index, field) in fields.unnamed.iter().enumerate() {
996 let ident = format_ident!("field_{index}");
997 bindings.push(ident.clone());
998 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
999 debug_fields.push(quote! {
1000 debug.field(#ident);
1001 });
1002 }
1003 debug_arms.push(quote! {
1004 #name::#variant_ident ( #(#bindings),* ) => {
1005 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1006 #(#debug_fields)*
1007 debug.finish()
1008 }
1009 });
1010 }
1011 }
1012 }
1013 DebugOutput {
1014 body: quote! {
1015 match self {
1016 #(#debug_arms),*
1017 }
1018 },
1019 generics: debug_generics,
1020 }
1021}