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))]
137pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
138 let input = parse_macro_input!(input as DeriveInput);
139 match expand(input, DeriveKind::Sensitive) {
140 Ok(tokens) => tokens.into(),
141 Err(err) => err.into_compile_error().into(),
142 }
143}
144
145#[proc_macro_derive(NotSensitive, attributes(not_sensitive))]
171pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
172 let input = parse_macro_input!(input as DeriveInput);
173 match expand_not_sensitive(input) {
174 Ok(tokens) => tokens.into(),
175 Err(err) => err.into_compile_error().into(),
176 }
177}
178
179fn reject_sensitivity_attrs(attrs: &[syn::Attribute], data: &Data, macro_name: &str) -> Result<()> {
185 let check_attr = |attr: &syn::Attribute| -> Result<()> {
186 if attr.path().is_ident("sensitive") {
187 return Err(syn::Error::new(
188 attr.span(),
189 format!("`#[sensitive]` attributes are not allowed on `{macro_name}` types"),
190 ));
191 }
192 if attr.path().is_ident("not_sensitive") {
193 return Err(syn::Error::new(
194 attr.span(),
195 format!(
196 "`#[not_sensitive]` attributes are not needed on `{macro_name}` types (the entire type is already non-sensitive)"
197 ),
198 ));
199 }
200 Ok(())
201 };
202
203 for attr in attrs {
204 check_attr(attr)?;
205 }
206
207 match data {
208 Data::Struct(data) => {
209 for field in &data.fields {
210 for attr in &field.attrs {
211 check_attr(attr)?;
212 }
213 }
214 }
215 Data::Enum(data) => {
216 for variant in &data.variants {
217 for attr in &variant.attrs {
218 check_attr(attr)?;
219 }
220 for field in &variant.fields {
221 for attr in &field.attrs {
222 check_attr(attr)?;
223 }
224 }
225 }
226 }
227 Data::Union(_) => {}
228 }
229
230 Ok(())
231}
232
233fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
234 let DeriveInput {
235 ident,
236 generics,
237 data,
238 attrs,
239 ..
240 } = input;
241
242 if let Data::Union(u) = &data {
244 return Err(syn::Error::new(
245 u.union_token.span(),
246 "`NotSensitive` cannot be derived for unions",
247 ));
248 }
249
250 reject_sensitivity_attrs(&attrs, &data, "NotSensitive")?;
251
252 let crate_root = crate_root();
253
254 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
257 let container_impl = quote! {
258 impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
259 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
260 self
261 }
262 }
263
264 impl #impl_generics #crate_root::DeclaredRedactable for #ident #ty_generics #where_clause {}
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
371fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
372 let DeriveInput {
373 ident,
374 generics,
375 data,
376 attrs,
377 ..
378 } = input;
379
380 if let Data::Union(u) = &data {
382 return Err(syn::Error::new(
383 u.union_token.span(),
384 "`NotSensitiveDisplay` cannot be derived for unions",
385 ));
386 }
387
388 reject_sensitivity_attrs(&attrs, &data, "NotSensitiveDisplay")?;
389
390 let crate_root = crate_root();
391
392 let (container_impl_generics, container_ty_generics, container_where_clause) =
397 generics.split_for_impl();
398 let container_impl = quote! {
399 impl #container_impl_generics #crate_root::RedactableWithMapper for #ident #container_ty_generics #container_where_clause {
400 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
401 self
402 }
403 }
404
405 impl #container_impl_generics #crate_root::DeclaredRedactable for #ident #container_ty_generics #container_where_clause {}
406 };
407
408 let mut display_generics = generics.clone();
411 let display_where_clause = display_generics.make_where_clause();
412 for param in generics.type_params() {
414 let ident = ¶m.ident;
415 display_where_clause
416 .predicates
417 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
418 }
419
420 let (display_impl_generics, display_ty_generics, display_where_clause) =
421 display_generics.split_for_impl();
422
423 let redacted_display_impl = quote! {
425 impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
426 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
427 ::core::fmt::Display::fmt(self, f)
428 }
429 }
430 };
431
432 #[cfg(feature = "slog")]
434 let slog_impl = {
435 let slog_crate = slog_crate()?;
436 let mut slog_generics = generics.clone();
437 let (_, ty_generics, _) = slog_generics.split_for_impl();
438 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
439 slog_generics
440 .make_where_clause()
441 .predicates
442 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
443 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
444 slog_generics.split_for_impl();
445 quote! {
446 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
447 fn serialize(
448 &self,
449 _record: &#slog_crate::Record<'_>,
450 key: #slog_crate::Key,
451 serializer: &mut dyn #slog_crate::Serializer,
452 ) -> #slog_crate::Result {
453 let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
454 serializer.emit_arguments(key, &format_args!("{}", redacted))
455 }
456 }
457
458 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
459 }
460 };
461
462 #[cfg(not(feature = "slog"))]
463 let slog_impl = quote! {};
464
465 #[cfg(feature = "tracing")]
467 let tracing_impl = {
468 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
469 generics.split_for_impl();
470 quote! {
471 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
472 }
473 };
474
475 #[cfg(not(feature = "tracing"))]
476 let tracing_impl = quote! {};
477
478 Ok(quote! {
479 #container_impl
480 #redacted_display_impl
481 #slog_impl
482 #tracing_impl
483 })
484}
485
486#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
513pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
514 let input = parse_macro_input!(input as DeriveInput);
515 match expand(input, DeriveKind::SensitiveDisplay) {
516 Ok(tokens) => tokens.into(),
517 Err(err) => err.into_compile_error().into(),
518 }
519}
520
521fn crate_root() -> proc_macro2::TokenStream {
526 match crate_name("redactable") {
527 Ok(FoundCrate::Itself) => quote! { crate },
528 Ok(FoundCrate::Name(name)) => {
529 let ident = format_ident!("{}", name);
530 quote! { ::#ident }
531 }
532 Err(_) => quote! { ::redactable },
533 }
534}
535
536#[cfg(feature = "slog")]
542fn slog_crate() -> Result<proc_macro2::TokenStream> {
543 match crate_name("slog") {
544 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
545 Ok(FoundCrate::Name(name)) => {
546 let ident = format_ident!("{}", name);
547 Ok(quote! { ::#ident })
548 }
549 Err(_) => {
550 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
551 syn::Error::new(
552 Span::call_site(),
553 "slog support is enabled, but no top-level `slog` crate was found. \
554Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
555`slog` as a direct dependency.",
556 )
557 })?;
558 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
559 syn::Error::new(
560 Span::call_site(),
561 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
562 )
563 })?;
564 Ok(quote! { #path })
565 }
566 }
567}
568
569fn crate_path(item: &str) -> proc_macro2::TokenStream {
570 let root = crate_root();
571 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
572 quote! { #root::#item_ident }
573}
574
575pub(crate) struct DeriveOutput {
579 pub(crate) redaction_body: TokenStream,
580 pub(crate) used_generics: Vec<Ident>,
581 pub(crate) policy_applicable_generics: Vec<Ident>,
582 pub(crate) debug_redacted_body: TokenStream,
583 pub(crate) debug_unredacted_body: TokenStream,
584 pub(crate) debug_unredacted_generics: Vec<Ident>,
585}
586
587struct DebugOutput {
588 body: TokenStream,
589 generics: Vec<Ident>,
590}
591
592enum DeriveKind {
597 Sensitive,
599 SensitiveDisplay,
601}
602
603#[allow(clippy::too_many_lines)]
604fn expand(input: DeriveInput, kind: DeriveKind) -> Result<TokenStream> {
605 let DeriveInput {
606 ident,
607 generics,
608 data,
609 attrs,
610 ..
611 } = input;
612
613 let ContainerOptions { dual } = parse_container_options(&attrs)?;
614
615 let crate_root = crate_root();
616
617 let dual_pairing_assert = if dual && generics.params.is_empty() {
625 match kind {
626 DeriveKind::Sensitive => quote! {
627 const _: fn() = || {
628 fn dual_requires_sensitive_display<T: #crate_root::RedactableWithFormatter>() {}
631 dual_requires_sensitive_display::<#ident>();
632 };
633 },
634 DeriveKind::SensitiveDisplay => quote! {
635 const _: fn() = || {
636 fn dual_requires_sensitive<T: #crate_root::RedactableWithMapper>() {}
639 dual_requires_sensitive::<#ident>();
640 };
641 },
642 }
643 } else {
644 quote! {}
645 };
646
647 if matches!(kind, DeriveKind::SensitiveDisplay) {
648 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
649 let redacted_display_generics =
650 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
651 let redacted_display_generics = add_debug_bounds(
652 redacted_display_generics,
653 &redacted_display_output.debug_generics,
654 );
655 let redacted_display_generics = add_policy_applicable_ref_bounds(
656 redacted_display_generics,
657 &redacted_display_output.policy_ref_generics,
658 );
659 let redacted_display_generics = add_redacted_display_bounds(
660 redacted_display_generics,
661 &redacted_display_output.nested_generics,
662 );
663 let (display_impl_generics, display_ty_generics, display_where_clause) =
664 redacted_display_generics.split_for_impl();
665 let redacted_display_body = redacted_display_output.body;
666 let declared_impl = if dual {
670 quote! {}
671 } else {
672 let (marker_impl_generics, marker_ty_generics, marker_where_clause) =
673 generics.split_for_impl();
674 quote! {
675 impl #marker_impl_generics #crate_root::DeclaredRedactable for #ident #marker_ty_generics #marker_where_clause {}
676 }
677 };
678 let redacted_display_impl = quote! {
679 impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
680 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
681 #redacted_display_body
682 }
683 }
684
685 #declared_impl
686 };
687 let to_redacted_output_impl = quote! {
688 impl #display_impl_generics #crate_root::ToRedactedOutput for #ident #display_ty_generics #display_where_clause {
689 fn to_redacted_output(&self) -> #crate_root::RedactedOutput {
690 #crate_root::RedactedOutput::Text(
691 #crate_root::RedactableWithFormatter::redacted_display(self).to_string(),
692 )
693 }
694 }
695 };
696
697 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
698 let debug_generics =
705 add_debug_bounds(redacted_display_generics.clone(), &debug_output.generics);
706 let (debug_impl_generics, debug_ty_generics, debug_where_clause) =
707 debug_generics.split_for_impl();
708 let debug_unredacted_body = debug_output.body;
709 let debug_impl = quote! {
710 impl #debug_impl_generics ::core::fmt::Debug for #ident #debug_ty_generics #debug_where_clause {
711 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
712 if ::core::cfg!(test) || #crate_root::__TESTING {
713 #debug_unredacted_body
714 } else {
715 #crate_root::RedactableWithFormatter::fmt_redacted(self, f)
716 }
717 }
718 }
719 };
720
721 let slog_impl = if dual {
723 quote! {}
724 } else {
725 #[cfg(feature = "slog")]
726 {
727 let slog_crate = slog_crate()?;
728 let mut slog_generics = generics;
729 let (_, ty_generics, _) = slog_generics.split_for_impl();
730 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
731 slog_generics
732 .make_where_clause()
733 .predicates
734 .push(parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
735 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
736 slog_generics.split_for_impl();
737 quote! {
738 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
739 fn serialize(
740 &self,
741 _record: &#slog_crate::Record<'_>,
742 key: #slog_crate::Key,
743 serializer: &mut dyn #slog_crate::Serializer,
744 ) -> #slog_crate::Result {
745 let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
746 serializer.emit_arguments(key, &format_args!("{}", redacted))
747 }
748 }
749
750 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
751 }
752 }
753
754 #[cfg(not(feature = "slog"))]
755 {
756 quote! {}
757 }
758 };
759
760 let tracing_impl = if dual {
761 quote! {}
762 } else {
763 #[cfg(feature = "tracing")]
764 {
765 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
766 redacted_display_generics.split_for_impl();
767 quote! {
768 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
769 }
770 }
771
772 #[cfg(not(feature = "tracing"))]
773 {
774 quote! {}
775 }
776 };
777
778 return Ok(quote! {
779 #redacted_display_impl
780 #to_redacted_output_impl
781 #debug_impl
782 #slog_impl
783 #tracing_impl
784 #dual_pairing_assert
785 });
786 }
787
788 let derive_output = match data {
791 Data::Struct(data) => derive_struct(&ident, data, &generics)?,
792 Data::Enum(data) => derive_enum(&ident, data, &generics)?,
793 Data::Union(u) => {
794 return Err(syn::Error::new(
795 u.union_token.span(),
796 "`Sensitive` cannot be derived for unions",
797 ));
798 }
799 };
800
801 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
802 let policy_generics =
803 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
804 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
805 let debug_unredacted_generics =
808 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
809 let (
810 debug_unredacted_impl_generics,
811 debug_unredacted_ty_generics,
812 debug_unredacted_where_clause,
813 ) = debug_unredacted_generics.split_for_impl();
814 let redaction_body = &derive_output.redaction_body;
815 let debug_redacted_body = &derive_output.debug_redacted_body;
816 let debug_unredacted_body = &derive_output.debug_unredacted_body;
817 let debug_impl = if dual {
826 quote! {}
827 } else {
828 quote! {
829 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
830 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
831 if ::core::cfg!(test) || #crate_root::__TESTING {
832 #debug_unredacted_body
833 } else {
834 #debug_redacted_body
835 }
836 }
837 }
838 }
839 };
840
841 #[cfg(feature = "slog")]
842 let slog_impl = {
843 let slog_crate = slog_crate()?;
844 let mut slog_generics = generics;
845 let slog_where_clause = slog_generics.make_where_clause();
846 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
847 slog_where_clause
848 .predicates
849 .push(parse_quote!(#self_ty: ::core::clone::Clone));
850 slog_where_clause
853 .predicates
854 .push(parse_quote!(#self_ty: ::serde::Serialize));
855 slog_where_clause
856 .predicates
857 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
858 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
859 slog_generics.split_for_impl();
860 quote! {
861 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
862 fn serialize(
863 &self,
864 _record: &#slog_crate::Record<'_>,
865 key: #slog_crate::Key,
866 serializer: &mut dyn #slog_crate::Serializer,
867 ) -> #slog_crate::Result {
868 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
869 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
870 }
871 }
872
873 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
874 }
875 };
876
877 #[cfg(not(feature = "slog"))]
878 let slog_impl = quote! {};
879
880 #[cfg(feature = "tracing")]
881 let tracing_impl = quote! {
882 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
883 };
884
885 #[cfg(not(feature = "tracing"))]
886 let tracing_impl = quote! {};
887
888 let trait_impl = quote! {
889 impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
890 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
891 use #crate_root::RedactableWithMapper as _;
892 #redaction_body
893 }
894 }
895
896 impl #impl_generics #crate_root::DeclaredRedactable for #ident #ty_generics #where_clause {}
897
898 #debug_impl
899
900 #slog_impl
901
902 #tracing_impl
903
904 #dual_pairing_assert
905 };
906 Ok(trait_impl)
907}
908
909fn derive_unredacted_debug(
910 name: &Ident,
911 data: &Data,
912 generics: &syn::Generics,
913) -> Result<DebugOutput> {
914 match data {
915 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
916 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
917 Data::Union(u) => Err(syn::Error::new(
918 u.union_token.span(),
919 "`SensitiveDisplay` cannot be derived for unions",
920 )),
921 }
922}
923
924fn derive_unredacted_debug_struct(
925 name: &Ident,
926 data: &DataStruct,
927 generics: &syn::Generics,
928) -> DebugOutput {
929 let mut debug_generics = Vec::new();
930 match &data.fields {
931 Fields::Named(fields) => {
932 let mut bindings = Vec::new();
933 let mut debug_fields = Vec::new();
934 for field in &fields.named {
935 let ident = field
936 .ident
937 .clone()
938 .expect("named field should have identifier");
939 bindings.push(ident.clone());
940 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
941 debug_fields.push(quote! {
942 debug.field(stringify!(#ident), #ident);
943 });
944 }
945 DebugOutput {
946 body: quote! {
947 match self {
948 Self { #(#bindings),* } => {
949 let mut debug = f.debug_struct(stringify!(#name));
950 #(#debug_fields)*
951 debug.finish()
952 }
953 }
954 },
955 generics: debug_generics,
956 }
957 }
958 Fields::Unnamed(fields) => {
959 let mut bindings = Vec::new();
960 let mut debug_fields = Vec::new();
961 for (index, field) in fields.unnamed.iter().enumerate() {
962 let ident = format_ident!("field_{index}");
963 bindings.push(ident.clone());
964 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
965 debug_fields.push(quote! {
966 debug.field(#ident);
967 });
968 }
969 DebugOutput {
970 body: quote! {
971 match self {
972 Self ( #(#bindings),* ) => {
973 let mut debug = f.debug_tuple(stringify!(#name));
974 #(#debug_fields)*
975 debug.finish()
976 }
977 }
978 },
979 generics: debug_generics,
980 }
981 }
982 Fields::Unit => DebugOutput {
983 body: quote! {
984 f.write_str(stringify!(#name))
985 },
986 generics: debug_generics,
987 },
988 }
989}
990
991fn derive_unredacted_debug_enum(
992 name: &Ident,
993 data: &DataEnum,
994 generics: &syn::Generics,
995) -> DebugOutput {
996 let mut debug_generics = Vec::new();
997 let mut debug_arms = Vec::new();
998 for variant in &data.variants {
999 let variant_ident = &variant.ident;
1000 match &variant.fields {
1001 Fields::Unit => {
1002 debug_arms.push(quote! {
1003 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
1004 });
1005 }
1006 Fields::Named(fields) => {
1007 let mut bindings = Vec::new();
1008 let mut debug_fields = Vec::new();
1009 for field in &fields.named {
1010 let ident = field
1011 .ident
1012 .clone()
1013 .expect("named field should have identifier");
1014 bindings.push(ident.clone());
1015 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1016 debug_fields.push(quote! {
1017 debug.field(stringify!(#ident), #ident);
1018 });
1019 }
1020 debug_arms.push(quote! {
1021 #name::#variant_ident { #(#bindings),* } => {
1022 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
1023 #(#debug_fields)*
1024 debug.finish()
1025 }
1026 });
1027 }
1028 Fields::Unnamed(fields) => {
1029 let mut bindings = Vec::new();
1030 let mut debug_fields = Vec::new();
1031 for (index, field) in fields.unnamed.iter().enumerate() {
1032 let ident = format_ident!("field_{index}");
1033 bindings.push(ident.clone());
1034 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1035 debug_fields.push(quote! {
1036 debug.field(#ident);
1037 });
1038 }
1039 debug_arms.push(quote! {
1040 #name::#variant_ident ( #(#bindings),* ) => {
1041 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1042 #(#debug_fields)*
1043 debug.finish()
1044 }
1045 });
1046 }
1047 }
1048 }
1049 DebugOutput {
1050 body: quote! {
1051 match self {
1052 #(#debug_arms),*
1053 }
1054 },
1055 generics: debug_generics,
1056 }
1057}