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::{
89 ContainerOptions, NotSensitiveDisplayOptions, parse_container_options,
90 parse_not_sensitive_display_options,
91};
92use derive_enum::derive_enum;
93use derive_struct::derive_struct;
94use generics::{
95 add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
96 add_policy_applicable_ref_bounds, add_redacted_display_bounds, collect_generics_from_type,
97};
98use redacted_display::derive_redacted_display;
99
100#[proc_macro_derive(Sensitive, attributes(sensitive, not_sensitive))]
138pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
139 let input = parse_macro_input!(input as DeriveInput);
140 match expand(input, SlogMode::RedactedJson) {
141 Ok(tokens) => tokens.into(),
142 Err(err) => err.into_compile_error().into(),
143 }
144}
145
146#[proc_macro_derive(NotSensitive, attributes(not_sensitive))]
173pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
174 let input = parse_macro_input!(input as DeriveInput);
175 match expand_not_sensitive(input) {
176 Ok(tokens) => tokens.into(),
177 Err(err) => err.into_compile_error().into(),
178 }
179}
180
181#[allow(clippy::too_many_lines)]
182fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
183 let DeriveInput {
184 ident,
185 generics,
186 data,
187 attrs,
188 ..
189 } = input;
190
191 if let Data::Union(u) = &data {
193 return Err(syn::Error::new(
194 u.union_token.span(),
195 "`NotSensitive` cannot be derived for unions",
196 ));
197 }
198
199 for attr in &attrs {
203 if attr.path().is_ident("sensitive") {
204 return Err(syn::Error::new(
205 attr.span(),
206 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
207 ));
208 }
209 if attr.path().is_ident("not_sensitive") {
210 return Err(syn::Error::new(
211 attr.span(),
212 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
213 ));
214 }
215 }
216
217 match &data {
218 Data::Struct(data) => {
219 for field in &data.fields {
220 for attr in &field.attrs {
221 if attr.path().is_ident("sensitive") {
222 return Err(syn::Error::new(
223 attr.span(),
224 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
225 ));
226 }
227 if attr.path().is_ident("not_sensitive") {
228 return Err(syn::Error::new(
229 attr.span(),
230 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
231 ));
232 }
233 }
234 }
235 }
236 Data::Enum(data) => {
237 for variant in &data.variants {
238 for field in &variant.fields {
239 for attr in &field.attrs {
240 if attr.path().is_ident("sensitive") {
241 return Err(syn::Error::new(
242 attr.span(),
243 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
244 ));
245 }
246 if attr.path().is_ident("not_sensitive") {
247 return Err(syn::Error::new(
248 attr.span(),
249 "`#[not_sensitive]` attributes are not needed on `NotSensitive` types (the entire type is already non-sensitive)",
250 ));
251 }
252 }
253 }
254 }
255 }
256 Data::Union(_) => unreachable!("unions rejected above"),
257 }
258
259 let crate_root = crate_root();
260
261 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
263 let container_impl = quote! {
264 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
265 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
266 self
267 }
268 }
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: ::core::fmt::Debug));
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 serializer.emit_arguments(key, &format_args!("{:?}", self))
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, attributes(not_sensitive_display))]
364pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
365 let input = parse_macro_input!(input as DeriveInput);
366 match expand_not_sensitive_display(input) {
367 Ok(tokens) => tokens.into(),
368 Err(err) => err.into_compile_error().into(),
369 }
370}
371
372#[allow(clippy::too_many_lines)]
373fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
374 let DeriveInput {
375 ident,
376 generics,
377 data,
378 attrs,
379 ..
380 } = input;
381
382 if let Data::Union(u) = &data {
384 return Err(syn::Error::new(
385 u.union_token.span(),
386 "`NotSensitiveDisplay` cannot be derived for unions",
387 ));
388 }
389
390 let mut sensitive_attr_spans = Vec::new();
392 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
393 sensitive_attr_spans.push(attr.span());
394 }
395
396 match &data {
397 Data::Struct(data) => {
398 for field in &data.fields {
399 if field
400 .attrs
401 .iter()
402 .any(|attr| attr.path().is_ident("sensitive"))
403 {
404 sensitive_attr_spans.push(field.span());
405 }
406 }
407 }
408 Data::Enum(data) => {
409 for variant in &data.variants {
410 for field in &variant.fields {
411 if field
412 .attrs
413 .iter()
414 .any(|attr| attr.path().is_ident("sensitive"))
415 {
416 sensitive_attr_spans.push(field.span());
417 }
418 }
419 }
420 }
421 Data::Union(_) => unreachable!("unions rejected above"),
422 }
423
424 if let Some(span) = sensitive_attr_spans.first() {
425 return Err(syn::Error::new(
426 *span,
427 "`#[sensitive]` attributes are not allowed on `NotSensitiveDisplay` types",
428 ));
429 }
430
431 let mut not_sensitive_attr_spans = Vec::new();
433 match &data {
434 Data::Struct(data) => {
435 for field in &data.fields {
436 if field
437 .attrs
438 .iter()
439 .any(|attr| attr.path().is_ident("not_sensitive"))
440 {
441 not_sensitive_attr_spans.push(field.span());
442 }
443 }
444 }
445 Data::Enum(data) => {
446 for variant in &data.variants {
447 for field in &variant.fields {
448 if field
449 .attrs
450 .iter()
451 .any(|attr| attr.path().is_ident("not_sensitive"))
452 {
453 not_sensitive_attr_spans.push(field.span());
454 }
455 }
456 }
457 }
458 Data::Union(_) => unreachable!("unions rejected above"),
459 }
460
461 if let Some(span) = not_sensitive_attr_spans.first() {
462 return Err(syn::Error::new(
463 *span,
464 "`#[not_sensitive]` attributes are not needed on `NotSensitiveDisplay` types (the entire type is already non-sensitive)",
465 ));
466 }
467
468 let NotSensitiveDisplayOptions { skip_debug } = parse_not_sensitive_display_options(&attrs)?;
469
470 let crate_root = crate_root();
471
472 let (container_impl_generics, container_ty_generics, container_where_clause) =
475 generics.split_for_impl();
476 let container_impl = quote! {
477 impl #container_impl_generics #crate_root::RedactableContainer for #ident #container_ty_generics #container_where_clause {
478 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
479 self
480 }
481 }
482 };
483
484 let mut display_generics = generics.clone();
487 let display_where_clause = display_generics.make_where_clause();
488 for param in generics.type_params() {
490 let ident = ¶m.ident;
491 display_where_clause
492 .predicates
493 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
494 }
495
496 let (display_impl_generics, display_ty_generics, display_where_clause) =
497 display_generics.split_for_impl();
498
499 let redacted_display_impl = quote! {
501 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
502 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
503 ::core::fmt::Display::fmt(self, f)
504 }
505 }
506 };
507
508 let debug_impl = if skip_debug {
510 quote! {}
511 } else {
512 let mut debug_generics = generics.clone();
514 let debug_where_clause = debug_generics.make_where_clause();
515 for param in generics.type_params() {
516 let ident = ¶m.ident;
517 debug_where_clause
518 .predicates
519 .push(syn::parse_quote!(#ident: ::core::fmt::Debug));
520 }
521 let (debug_impl_generics, debug_ty_generics, debug_where_clause) =
522 debug_generics.split_for_impl();
523
524 let debug_unredacted_body = match &data {
526 Data::Struct(data) => derive_unredacted_debug_struct(&ident, data, &generics).body,
527 Data::Enum(data) => derive_unredacted_debug_enum(&ident, data, &generics).body,
528 Data::Union(_) => unreachable!(),
529 };
530
531 quote! {
532 #[cfg(any(test, feature = "testing"))]
533 impl #debug_impl_generics ::core::fmt::Debug for #ident #debug_ty_generics #debug_where_clause {
534 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
535 #debug_unredacted_body
536 }
537 }
538
539 #[cfg(not(any(test, feature = "testing")))]
540 impl #display_impl_generics ::core::fmt::Debug for #ident #display_ty_generics #display_where_clause {
541 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
542 ::core::fmt::Display::fmt(self, f)
543 }
544 }
545 }
546 };
547
548 #[cfg(feature = "slog")]
550 let slog_impl = {
551 let slog_crate = slog_crate()?;
552 let mut slog_generics = generics;
553 let (_, ty_generics, _) = slog_generics.split_for_impl();
554 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
555 slog_generics
556 .make_where_clause()
557 .predicates
558 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableDisplay));
559 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
560 slog_generics.split_for_impl();
561 quote! {
562 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
563 fn serialize(
564 &self,
565 _record: &#slog_crate::Record<'_>,
566 key: #slog_crate::Key,
567 serializer: &mut dyn #slog_crate::Serializer,
568 ) -> #slog_crate::Result {
569 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
570 serializer.emit_arguments(key, &format_args!("{}", redacted))
571 }
572 }
573
574 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
575 }
576 };
577
578 #[cfg(not(feature = "slog"))]
579 let slog_impl = quote! {};
580
581 #[cfg(feature = "tracing")]
583 let tracing_impl = {
584 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
585 display_generics.split_for_impl();
586 quote! {
587 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
588 }
589 };
590
591 #[cfg(not(feature = "tracing"))]
592 let tracing_impl = quote! {};
593
594 Ok(quote! {
595 #container_impl
596 #redacted_display_impl
597 #debug_impl
598 #slog_impl
599 #tracing_impl
600 })
601}
602
603#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
631pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
632 let input = parse_macro_input!(input as DeriveInput);
633 match expand(input, SlogMode::RedactedDisplay) {
634 Ok(tokens) => tokens.into(),
635 Err(err) => err.into_compile_error().into(),
636 }
637}
638
639fn crate_root() -> proc_macro2::TokenStream {
644 match crate_name("redactable") {
645 Ok(FoundCrate::Itself) => quote! { crate },
646 Ok(FoundCrate::Name(name)) => {
647 let ident = format_ident!("{}", name);
648 quote! { ::#ident }
649 }
650 Err(_) => quote! { ::redactable },
651 }
652}
653
654#[cfg(feature = "slog")]
660fn slog_crate() -> Result<proc_macro2::TokenStream> {
661 match crate_name("slog") {
662 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
663 Ok(FoundCrate::Name(name)) => {
664 let ident = format_ident!("{}", name);
665 Ok(quote! { ::#ident })
666 }
667 Err(_) => {
668 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
669 syn::Error::new(
670 Span::call_site(),
671 "slog support is enabled, but no top-level `slog` crate was found. \
672Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
673`slog` as a direct dependency.",
674 )
675 })?;
676 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
677 syn::Error::new(
678 Span::call_site(),
679 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
680 )
681 })?;
682 Ok(quote! { #path })
683 }
684 }
685}
686
687fn crate_path(item: &str) -> proc_macro2::TokenStream {
688 let root = crate_root();
689 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
690 quote! { #root::#item_ident }
691}
692
693struct DeriveOutput {
694 redaction_body: TokenStream,
695 used_generics: Vec<Ident>,
696 policy_applicable_generics: Vec<Ident>,
697 debug_redacted_body: TokenStream,
698 debug_redacted_generics: Vec<Ident>,
699 debug_unredacted_body: TokenStream,
700 debug_unredacted_generics: Vec<Ident>,
701 redacted_display_body: Option<TokenStream>,
702 redacted_display_generics: Vec<Ident>,
703 redacted_display_debug_generics: Vec<Ident>,
704 redacted_display_policy_ref_generics: Vec<Ident>,
705 redacted_display_nested_generics: Vec<Ident>,
706}
707
708struct DebugOutput {
709 body: TokenStream,
710 generics: Vec<Ident>,
711}
712
713enum SlogMode {
714 RedactedJson,
715 RedactedDisplay,
716}
717
718#[allow(clippy::too_many_lines, clippy::redundant_clone)]
719fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
720 let DeriveInput {
721 ident,
722 generics,
723 data,
724 attrs,
725 ..
726 } = input;
727
728 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
729
730 let crate_root = crate_root();
731
732 if matches!(slog_mode, SlogMode::RedactedDisplay) {
733 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
734 let redacted_display_generics =
735 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
736 let redacted_display_generics = add_debug_bounds(
737 redacted_display_generics,
738 &redacted_display_output.debug_generics,
739 );
740 let redacted_display_generics = add_policy_applicable_ref_bounds(
741 redacted_display_generics,
742 &redacted_display_output.policy_ref_generics,
743 );
744 let redacted_display_generics = add_redacted_display_bounds(
745 redacted_display_generics,
746 &redacted_display_output.nested_generics,
747 );
748 let (display_impl_generics, display_ty_generics, display_where_clause) =
749 redacted_display_generics.split_for_impl();
750 let redacted_display_body = redacted_display_output.body;
751 let redacted_display_impl = quote! {
752 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
753 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
754 #redacted_display_body
755 }
756 }
757 };
758 let debug_impl = if skip_debug {
759 quote! {}
760 } else {
761 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
762 let debug_unredacted_generics =
763 add_debug_bounds(generics.clone(), &debug_output.generics);
764 let (
765 debug_unredacted_impl_generics,
766 debug_unredacted_ty_generics,
767 debug_unredacted_where_clause,
768 ) = debug_unredacted_generics.split_for_impl();
769 let (
770 debug_redacted_impl_generics,
771 debug_redacted_ty_generics,
772 debug_redacted_where_clause,
773 ) = redacted_display_generics.split_for_impl();
774 let debug_unredacted_body = debug_output.body;
775 quote! {
776 #[cfg(any(test, feature = "testing"))]
777 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
778 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
779 #debug_unredacted_body
780 }
781 }
782
783 #[cfg(not(any(test, feature = "testing")))]
784 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
785 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
786 #crate_root::RedactableDisplay::fmt_redacted(self, f)
787 }
788 }
789 }
790 };
791
792 #[cfg(feature = "slog")]
795 let slog_impl = {
796 let slog_crate = slog_crate()?;
797 let mut slog_generics = generics;
798 let (_, ty_generics, _) = slog_generics.split_for_impl();
800 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
801 slog_generics
802 .make_where_clause()
803 .predicates
804 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
805 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
806 slog_generics.split_for_impl();
807 quote! {
808 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
809 fn serialize(
810 &self,
811 _record: &#slog_crate::Record<'_>,
812 key: #slog_crate::Key,
813 serializer: &mut dyn #slog_crate::Serializer,
814 ) -> #slog_crate::Result {
815 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
816 serializer.emit_arguments(key, &format_args!("{}", redacted))
817 }
818 }
819
820 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
821 }
822 };
823
824 #[cfg(not(feature = "slog"))]
825 let slog_impl = quote! {};
826
827 #[cfg(feature = "tracing")]
828 let tracing_impl = {
829 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
830 redacted_display_generics.split_for_impl();
831 quote! {
832 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
833 }
834 };
835
836 #[cfg(not(feature = "tracing"))]
837 let tracing_impl = quote! {};
838
839 return Ok(quote! {
840 #redacted_display_impl
841 #debug_impl
842 #slog_impl
843 #tracing_impl
844 });
845 }
846
847 let derive_output = match &data {
851 Data::Struct(data) => {
852 let output = derive_struct(&ident, data.clone(), &generics)?;
853 DeriveOutput {
854 redaction_body: output.redaction_body,
855 used_generics: output.used_generics,
856 policy_applicable_generics: output.policy_applicable_generics,
857 debug_redacted_body: output.debug_redacted_body,
858 debug_redacted_generics: output.debug_redacted_generics,
859 debug_unredacted_body: output.debug_unredacted_body,
860 debug_unredacted_generics: output.debug_unredacted_generics,
861 redacted_display_body: None,
862 redacted_display_generics: Vec::new(),
863 redacted_display_debug_generics: Vec::new(),
864 redacted_display_policy_ref_generics: Vec::new(),
865 redacted_display_nested_generics: Vec::new(),
866 }
867 }
868 Data::Enum(data) => {
869 let output = derive_enum(&ident, data.clone(), &generics)?;
870 DeriveOutput {
871 redaction_body: output.redaction_body,
872 used_generics: output.used_generics,
873 policy_applicable_generics: output.policy_applicable_generics,
874 debug_redacted_body: output.debug_redacted_body,
875 debug_redacted_generics: output.debug_redacted_generics,
876 debug_unredacted_body: output.debug_unredacted_body,
877 debug_unredacted_generics: output.debug_unredacted_generics,
878 redacted_display_body: None,
879 redacted_display_generics: Vec::new(),
880 redacted_display_debug_generics: Vec::new(),
881 redacted_display_policy_ref_generics: Vec::new(),
882 redacted_display_nested_generics: Vec::new(),
883 }
884 }
885 Data::Union(u) => {
886 return Err(syn::Error::new(
887 u.union_token.span(),
888 "`Sensitive` cannot be derived for unions",
889 ));
890 }
891 };
892
893 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
894 let policy_generics =
895 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
896 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
897 let debug_redacted_generics =
898 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
899 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
900 debug_redacted_generics.split_for_impl();
901 let debug_unredacted_generics =
902 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
903 let (
904 debug_unredacted_impl_generics,
905 debug_unredacted_ty_generics,
906 debug_unredacted_where_clause,
907 ) = debug_unredacted_generics.split_for_impl();
908 let redaction_body = &derive_output.redaction_body;
909 let debug_redacted_body = &derive_output.debug_redacted_body;
910 let debug_unredacted_body = &derive_output.debug_unredacted_body;
911 let debug_impl = if skip_debug {
912 quote! {}
913 } else {
914 quote! {
915 #[cfg(any(test, feature = "testing"))]
916 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
917 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
918 #debug_unredacted_body
919 }
920 }
921
922 #[cfg(not(any(test, feature = "testing")))]
923 #[allow(unused_variables)]
924 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
925 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
926 #debug_redacted_body
927 }
928 }
929 }
930 };
931
932 let redacted_display_body = derive_output.redacted_display_body.as_ref();
933 let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
934 let redacted_display_generics =
935 add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
936 let redacted_display_generics = add_debug_bounds(
937 redacted_display_generics,
938 &derive_output.redacted_display_debug_generics,
939 );
940 let redacted_display_generics = add_policy_applicable_ref_bounds(
941 redacted_display_generics,
942 &derive_output.redacted_display_policy_ref_generics,
943 );
944 let redacted_display_generics = add_redacted_display_bounds(
945 redacted_display_generics,
946 &derive_output.redacted_display_nested_generics,
947 );
948 let (display_impl_generics, display_ty_generics, display_where_clause) =
949 redacted_display_generics.split_for_impl();
950 let redacted_display_body = redacted_display_body
951 .cloned()
952 .unwrap_or_else(TokenStream::new);
953 quote! {
954 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
955 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
956 #redacted_display_body
957 }
958 }
959 }
960 } else {
961 quote! {}
962 };
963
964 #[cfg(feature = "slog")]
967 let slog_impl = {
968 let slog_crate = slog_crate()?;
969 let mut slog_generics = generics;
970 let slog_where_clause = slog_generics.make_where_clause();
971 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
972 match slog_mode {
973 SlogMode::RedactedJson => {
974 slog_where_clause
975 .predicates
976 .push(parse_quote!(#self_ty: ::core::clone::Clone));
977 slog_where_clause
980 .predicates
981 .push(parse_quote!(#self_ty: ::serde::Serialize));
982 slog_where_clause
983 .predicates
984 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
985 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
986 slog_generics.split_for_impl();
987 quote! {
988 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
989 fn serialize(
990 &self,
991 _record: &#slog_crate::Record<'_>,
992 key: #slog_crate::Key,
993 serializer: &mut dyn #slog_crate::Serializer,
994 ) -> #slog_crate::Result {
995 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
996 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
997 }
998 }
999
1000 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
1001 }
1002 }
1003 SlogMode::RedactedDisplay => {
1004 slog_where_clause
1005 .predicates
1006 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
1007 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
1008 slog_generics.split_for_impl();
1009 quote! {
1010 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
1011 fn serialize(
1012 &self,
1013 _record: &#slog_crate::Record<'_>,
1014 key: #slog_crate::Key,
1015 serializer: &mut dyn #slog_crate::Serializer,
1016 ) -> #slog_crate::Result {
1017 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
1018 serializer.emit_arguments(key, &format_args!("{}", redacted))
1019 }
1020 }
1021
1022 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
1023 }
1024 }
1025 }
1026 };
1027
1028 #[cfg(not(feature = "slog"))]
1029 let slog_impl = quote! {};
1030
1031 #[cfg(feature = "tracing")]
1032 let tracing_impl = quote! {
1033 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
1034 };
1035
1036 #[cfg(not(feature = "tracing"))]
1037 let tracing_impl = quote! {};
1038
1039 let trait_impl = quote! {
1040 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
1041 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
1042 use #crate_root::RedactableContainer as _;
1043 #redaction_body
1044 }
1045 }
1046
1047 #debug_impl
1048
1049 #redacted_display_impl
1050
1051 #slog_impl
1052
1053 #tracing_impl
1054
1055 };
1058 Ok(trait_impl)
1059}
1060
1061fn derive_unredacted_debug(
1062 name: &Ident,
1063 data: &Data,
1064 generics: &syn::Generics,
1065) -> Result<DebugOutput> {
1066 match data {
1067 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
1068 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
1069 Data::Union(u) => Err(syn::Error::new(
1070 u.union_token.span(),
1071 "`SensitiveDisplay` cannot be derived for unions",
1072 )),
1073 }
1074}
1075
1076fn derive_unredacted_debug_struct(
1077 name: &Ident,
1078 data: &DataStruct,
1079 generics: &syn::Generics,
1080) -> DebugOutput {
1081 let mut debug_generics = Vec::new();
1082 match &data.fields {
1083 Fields::Named(fields) => {
1084 let mut bindings = Vec::new();
1085 let mut debug_fields = Vec::new();
1086 for field in &fields.named {
1087 let ident = field
1088 .ident
1089 .clone()
1090 .expect("named field should have identifier");
1091 bindings.push(ident.clone());
1092 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1093 debug_fields.push(quote! {
1094 debug.field(stringify!(#ident), #ident);
1095 });
1096 }
1097 DebugOutput {
1098 body: quote! {
1099 match self {
1100 Self { #(#bindings),* } => {
1101 let mut debug = f.debug_struct(stringify!(#name));
1102 #(#debug_fields)*
1103 debug.finish()
1104 }
1105 }
1106 },
1107 generics: debug_generics,
1108 }
1109 }
1110 Fields::Unnamed(fields) => {
1111 let mut bindings = Vec::new();
1112 let mut debug_fields = Vec::new();
1113 for (index, field) in fields.unnamed.iter().enumerate() {
1114 let ident = format_ident!("field_{index}");
1115 bindings.push(ident.clone());
1116 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1117 debug_fields.push(quote! {
1118 debug.field(#ident);
1119 });
1120 }
1121 DebugOutput {
1122 body: quote! {
1123 match self {
1124 Self ( #(#bindings),* ) => {
1125 let mut debug = f.debug_tuple(stringify!(#name));
1126 #(#debug_fields)*
1127 debug.finish()
1128 }
1129 }
1130 },
1131 generics: debug_generics,
1132 }
1133 }
1134 Fields::Unit => DebugOutput {
1135 body: quote! {
1136 f.write_str(stringify!(#name))
1137 },
1138 generics: debug_generics,
1139 },
1140 }
1141}
1142
1143fn derive_unredacted_debug_enum(
1144 name: &Ident,
1145 data: &DataEnum,
1146 generics: &syn::Generics,
1147) -> DebugOutput {
1148 let mut debug_generics = Vec::new();
1149 let mut debug_arms = Vec::new();
1150 for variant in &data.variants {
1151 let variant_ident = &variant.ident;
1152 match &variant.fields {
1153 Fields::Unit => {
1154 debug_arms.push(quote! {
1155 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
1156 });
1157 }
1158 Fields::Named(fields) => {
1159 let mut bindings = Vec::new();
1160 let mut debug_fields = Vec::new();
1161 for field in &fields.named {
1162 let ident = field
1163 .ident
1164 .clone()
1165 .expect("named field should have identifier");
1166 bindings.push(ident.clone());
1167 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1168 debug_fields.push(quote! {
1169 debug.field(stringify!(#ident), #ident);
1170 });
1171 }
1172 debug_arms.push(quote! {
1173 #name::#variant_ident { #(#bindings),* } => {
1174 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
1175 #(#debug_fields)*
1176 debug.finish()
1177 }
1178 });
1179 }
1180 Fields::Unnamed(fields) => {
1181 let mut bindings = Vec::new();
1182 let mut debug_fields = Vec::new();
1183 for (index, field) in fields.unnamed.iter().enumerate() {
1184 let ident = format_ident!("field_{index}");
1185 bindings.push(ident.clone());
1186 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1187 debug_fields.push(quote! {
1188 debug.field(#ident);
1189 });
1190 }
1191 debug_arms.push(quote! {
1192 #name::#variant_ident ( #(#bindings),* ) => {
1193 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1194 #(#debug_fields)*
1195 debug.finish()
1196 }
1197 });
1198 }
1199 }
1200 }
1201 DebugOutput {
1202 body: quote! {
1203 match self {
1204 #(#debug_arms),*
1205 }
1206 },
1207 generics: debug_generics,
1208 }
1209}