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