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