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