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, has_display_template};
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)]
151pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
152 let input = parse_macro_input!(input as DeriveInput);
153 let ident = input.ident;
154 let generics = input.generics;
155 let attrs = input.attrs;
156 let data = input.data;
157
158 let mut sensitive_attr_spans = Vec::new();
159 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
160 sensitive_attr_spans.push(attr.span());
161 }
162
163 match &data {
164 Data::Struct(data) => {
165 for field in &data.fields {
166 if field
167 .attrs
168 .iter()
169 .any(|attr| attr.path().is_ident("sensitive"))
170 {
171 sensitive_attr_spans.push(field.span());
172 }
173 }
174 }
175 Data::Enum(data) => {
176 for variant in &data.variants {
177 for field in &variant.fields {
178 if field
179 .attrs
180 .iter()
181 .any(|attr| attr.path().is_ident("sensitive"))
182 {
183 sensitive_attr_spans.push(field.span());
184 }
185 }
186 }
187 }
188 Data::Union(data) => {
189 return syn::Error::new(
190 data.union_token.span(),
191 "`NotSensitive` cannot be derived for unions",
192 )
193 .into_compile_error()
194 .into();
195 }
196 }
197
198 if let Some(span) = sensitive_attr_spans.first() {
199 return syn::Error::new(
200 *span,
201 "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
202 )
203 .into_compile_error()
204 .into();
205 }
206 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
207 let crate_root = crate_root();
208
209 let tokens = quote! {
210 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
211 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
212 self
213 }
214 }
215 };
216 tokens.into()
217}
218
219#[proc_macro_derive(NotSensitiveDisplay, attributes(not_sensitive_display))]
260pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
261 let input = parse_macro_input!(input as DeriveInput);
262 match expand_not_sensitive_display(input) {
263 Ok(tokens) => tokens.into(),
264 Err(err) => err.into_compile_error().into(),
265 }
266}
267
268#[allow(clippy::too_many_lines)]
269fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
270 let DeriveInput {
271 ident,
272 generics,
273 data,
274 attrs,
275 ..
276 } = input;
277
278 if let Data::Union(u) = &data {
280 return Err(syn::Error::new(
281 u.union_token.span(),
282 "`NotSensitiveDisplay` cannot be derived for unions",
283 ));
284 }
285
286 let mut sensitive_attr_spans = Vec::new();
288 if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
289 sensitive_attr_spans.push(attr.span());
290 }
291
292 match &data {
293 Data::Struct(data) => {
294 for field in &data.fields {
295 if field
296 .attrs
297 .iter()
298 .any(|attr| attr.path().is_ident("sensitive"))
299 {
300 sensitive_attr_spans.push(field.span());
301 }
302 }
303 }
304 Data::Enum(data) => {
305 for variant in &data.variants {
306 for field in &variant.fields {
307 if field
308 .attrs
309 .iter()
310 .any(|attr| attr.path().is_ident("sensitive"))
311 {
312 sensitive_attr_spans.push(field.span());
313 }
314 }
315 }
316 }
317 Data::Union(_) => unreachable!("unions rejected above"),
318 }
319
320 if let Some(span) = sensitive_attr_spans.first() {
321 return Err(syn::Error::new(
322 *span,
323 "`#[sensitive]` attributes are not allowed on `NotSensitiveDisplay` types",
324 ));
325 }
326
327 let NotSensitiveDisplayOptions { skip_debug } = parse_not_sensitive_display_options(&attrs)?;
328
329 let crate_root = crate_root();
330
331 let has_template = has_display_template(&attrs)?;
333
334 let (container_impl_generics, container_ty_generics, container_where_clause) =
337 generics.split_for_impl();
338 let container_impl = quote! {
339 impl #container_impl_generics #crate_root::RedactableContainer for #ident #container_ty_generics #container_where_clause {
340 fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
341 self
342 }
343 }
344 };
345
346 if has_template {
348 return expand_not_sensitive_display_with_template(
349 &ident,
350 generics,
351 &data,
352 &attrs,
353 skip_debug,
354 container_impl,
355 );
356 }
357
358 let mut display_generics = generics.clone();
361 let display_where_clause = display_generics.make_where_clause();
362 for param in generics.type_params() {
364 let ident = ¶m.ident;
365 display_where_clause
366 .predicates
367 .push(syn::parse_quote!(#ident: ::core::fmt::Display));
368 }
369
370 let (display_impl_generics, display_ty_generics, display_where_clause) =
371 display_generics.split_for_impl();
372
373 let redacted_display_impl = quote! {
375 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
376 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
377 ::core::fmt::Display::fmt(self, f)
378 }
379 }
380 };
381
382 let debug_impl = if skip_debug {
384 quote! {}
385 } else {
386 let mut debug_generics = generics.clone();
388 let debug_where_clause = debug_generics.make_where_clause();
389 for param in generics.type_params() {
390 let ident = ¶m.ident;
391 debug_where_clause
392 .predicates
393 .push(syn::parse_quote!(#ident: ::core::fmt::Debug));
394 }
395 let (debug_impl_generics, debug_ty_generics, debug_where_clause) =
396 debug_generics.split_for_impl();
397
398 let debug_unredacted_body = match &data {
400 Data::Struct(data) => derive_unredacted_debug_struct(&ident, data, &generics).body,
401 Data::Enum(data) => derive_unredacted_debug_enum(&ident, data, &generics).body,
402 Data::Union(_) => unreachable!(),
403 };
404
405 quote! {
406 #[cfg(any(test, feature = "testing"))]
407 impl #debug_impl_generics ::core::fmt::Debug for #ident #debug_ty_generics #debug_where_clause {
408 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
409 #debug_unredacted_body
410 }
411 }
412
413 #[cfg(not(any(test, feature = "testing")))]
414 impl #display_impl_generics ::core::fmt::Debug for #ident #display_ty_generics #display_where_clause {
415 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
416 ::core::fmt::Display::fmt(self, f)
417 }
418 }
419 }
420 };
421
422 #[cfg(feature = "slog")]
424 let slog_impl = {
425 let slog_crate = slog_crate()?;
426 let mut slog_generics = generics;
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::RedactableDisplay));
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::RedactableDisplay::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 display_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 #debug_impl
472 #slog_impl
473 #tracing_impl
474 })
475}
476
477#[allow(clippy::too_many_lines, clippy::redundant_clone)]
482fn expand_not_sensitive_display_with_template(
483 ident: &Ident,
484 generics: syn::Generics,
485 data: &Data,
486 attrs: &[syn::Attribute],
487 skip_debug: bool,
488 container_impl: TokenStream,
489) -> Result<TokenStream> {
490 let crate_root = crate_root();
491
492 let redacted_display_output = derive_redacted_display(ident, data, attrs, &generics)?;
494 let redacted_display_generics =
495 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
496 let redacted_display_generics = add_debug_bounds(
497 redacted_display_generics,
498 &redacted_display_output.debug_generics,
499 );
500 let redacted_display_generics = add_policy_applicable_ref_bounds(
501 redacted_display_generics,
502 &redacted_display_output.policy_ref_generics,
503 );
504 let redacted_display_generics = add_redacted_display_bounds(
505 redacted_display_generics,
506 &redacted_display_output.nested_generics,
507 );
508 let (display_impl_generics, display_ty_generics, display_where_clause) =
509 redacted_display_generics.split_for_impl();
510 let redacted_display_body = redacted_display_output.body;
511 let redacted_display_impl = quote! {
512 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
513 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
514 #redacted_display_body
515 }
516 }
517 };
518
519 let debug_impl = if skip_debug {
523 quote! {}
524 } else {
525 let debug_output = derive_unredacted_debug(ident, data, &generics)?;
526 let debug_unredacted_generics = add_debug_bounds(generics.clone(), &debug_output.generics);
527 let (
528 debug_unredacted_impl_generics,
529 debug_unredacted_ty_generics,
530 debug_unredacted_where_clause,
531 ) = debug_unredacted_generics.split_for_impl();
532 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
533 redacted_display_generics.split_for_impl();
534 let debug_unredacted_body = debug_output.body;
535 quote! {
536 #[cfg(any(test, feature = "testing"))]
537 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
538 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
539 #debug_unredacted_body
540 }
541 }
542
543 #[cfg(not(any(test, feature = "testing")))]
544 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
545 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
546 #crate_root::RedactableDisplay::fmt_redacted(self, f)
547 }
548 }
549 }
550 };
551
552 #[cfg(feature = "slog")]
554 let slog_impl = {
555 let slog_crate = slog_crate()?;
556 let mut slog_generics = generics;
557 let (_, ty_generics, _) = slog_generics.split_for_impl();
558 let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
559 slog_generics
560 .make_where_clause()
561 .predicates
562 .push(syn::parse_quote!(#self_ty: #crate_root::RedactableDisplay));
563 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
564 slog_generics.split_for_impl();
565 quote! {
566 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
567 fn serialize(
568 &self,
569 _record: &#slog_crate::Record<'_>,
570 key: #slog_crate::Key,
571 serializer: &mut dyn #slog_crate::Serializer,
572 ) -> #slog_crate::Result {
573 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
574 serializer.emit_arguments(key, &format_args!("{}", redacted))
575 }
576 }
577
578 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
579 }
580 };
581
582 #[cfg(not(feature = "slog"))]
583 let slog_impl = quote! {};
584
585 #[cfg(feature = "tracing")]
587 let tracing_impl = {
588 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
589 redacted_display_generics.split_for_impl();
590 quote! {
591 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
592 }
593 };
594
595 #[cfg(not(feature = "tracing"))]
596 let tracing_impl = quote! {};
597
598 Ok(quote! {
599 #container_impl
600 #redacted_display_impl
601 #debug_impl
602 #slog_impl
603 #tracing_impl
604 })
605}
606
607#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
635pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
636 let input = parse_macro_input!(input as DeriveInput);
637 match expand(input, SlogMode::RedactedDisplay) {
638 Ok(tokens) => tokens.into(),
639 Err(err) => err.into_compile_error().into(),
640 }
641}
642
643fn crate_root() -> proc_macro2::TokenStream {
648 match crate_name("redactable") {
649 Ok(FoundCrate::Itself) => quote! { crate },
650 Ok(FoundCrate::Name(name)) => {
651 let ident = format_ident!("{}", name);
652 quote! { ::#ident }
653 }
654 Err(_) => quote! { ::redactable },
655 }
656}
657
658#[cfg(feature = "slog")]
664fn slog_crate() -> Result<proc_macro2::TokenStream> {
665 match crate_name("slog") {
666 Ok(FoundCrate::Itself) => Ok(quote! { crate }),
667 Ok(FoundCrate::Name(name)) => {
668 let ident = format_ident!("{}", name);
669 Ok(quote! { ::#ident })
670 }
671 Err(_) => {
672 let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
673 syn::Error::new(
674 Span::call_site(),
675 "slog support is enabled, but no top-level `slog` crate was found. \
676Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
677`slog` as a direct dependency.",
678 )
679 })?;
680 let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
681 syn::Error::new(
682 Span::call_site(),
683 format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
684 )
685 })?;
686 Ok(quote! { #path })
687 }
688 }
689}
690
691fn crate_path(item: &str) -> proc_macro2::TokenStream {
692 let root = crate_root();
693 let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
694 quote! { #root::#item_ident }
695}
696
697struct DeriveOutput {
698 redaction_body: TokenStream,
699 used_generics: Vec<Ident>,
700 policy_applicable_generics: Vec<Ident>,
701 debug_redacted_body: TokenStream,
702 debug_redacted_generics: Vec<Ident>,
703 debug_unredacted_body: TokenStream,
704 debug_unredacted_generics: Vec<Ident>,
705 redacted_display_body: Option<TokenStream>,
706 redacted_display_generics: Vec<Ident>,
707 redacted_display_debug_generics: Vec<Ident>,
708 redacted_display_policy_ref_generics: Vec<Ident>,
709 redacted_display_nested_generics: Vec<Ident>,
710}
711
712struct DebugOutput {
713 body: TokenStream,
714 generics: Vec<Ident>,
715}
716
717enum SlogMode {
718 RedactedJson,
719 RedactedDisplay,
720}
721
722#[allow(clippy::too_many_lines, clippy::redundant_clone)]
723fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
724 let DeriveInput {
725 ident,
726 generics,
727 data,
728 attrs,
729 ..
730 } = input;
731
732 let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
733
734 let crate_root = crate_root();
735
736 if matches!(slog_mode, SlogMode::RedactedDisplay) {
737 let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
738 let redacted_display_generics =
739 add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
740 let redacted_display_generics = add_debug_bounds(
741 redacted_display_generics,
742 &redacted_display_output.debug_generics,
743 );
744 let redacted_display_generics = add_policy_applicable_ref_bounds(
745 redacted_display_generics,
746 &redacted_display_output.policy_ref_generics,
747 );
748 let redacted_display_generics = add_redacted_display_bounds(
749 redacted_display_generics,
750 &redacted_display_output.nested_generics,
751 );
752 let (display_impl_generics, display_ty_generics, display_where_clause) =
753 redacted_display_generics.split_for_impl();
754 let redacted_display_body = redacted_display_output.body;
755 let redacted_display_impl = quote! {
756 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
757 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
758 #redacted_display_body
759 }
760 }
761 };
762 let debug_impl = if skip_debug {
763 quote! {}
764 } else {
765 let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
766 let debug_unredacted_generics =
767 add_debug_bounds(generics.clone(), &debug_output.generics);
768 let (
769 debug_unredacted_impl_generics,
770 debug_unredacted_ty_generics,
771 debug_unredacted_where_clause,
772 ) = debug_unredacted_generics.split_for_impl();
773 let (
774 debug_redacted_impl_generics,
775 debug_redacted_ty_generics,
776 debug_redacted_where_clause,
777 ) = redacted_display_generics.split_for_impl();
778 let debug_unredacted_body = debug_output.body;
779 quote! {
780 #[cfg(any(test, feature = "testing"))]
781 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
782 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
783 #debug_unredacted_body
784 }
785 }
786
787 #[cfg(not(any(test, feature = "testing")))]
788 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
789 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
790 #crate_root::RedactableDisplay::fmt_redacted(self, f)
791 }
792 }
793 }
794 };
795
796 #[cfg(feature = "slog")]
799 let slog_impl = {
800 let slog_crate = slog_crate()?;
801 let mut slog_generics = generics;
802 let (_, ty_generics, _) = slog_generics.split_for_impl();
804 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
805 slog_generics
806 .make_where_clause()
807 .predicates
808 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
809 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
810 slog_generics.split_for_impl();
811 quote! {
812 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
813 fn serialize(
814 &self,
815 _record: &#slog_crate::Record<'_>,
816 key: #slog_crate::Key,
817 serializer: &mut dyn #slog_crate::Serializer,
818 ) -> #slog_crate::Result {
819 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
820 serializer.emit_arguments(key, &format_args!("{}", redacted))
821 }
822 }
823
824 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
825 }
826 };
827
828 #[cfg(not(feature = "slog"))]
829 let slog_impl = quote! {};
830
831 #[cfg(feature = "tracing")]
832 let tracing_impl = {
833 let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
834 redacted_display_generics.split_for_impl();
835 quote! {
836 impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
837 }
838 };
839
840 #[cfg(not(feature = "tracing"))]
841 let tracing_impl = quote! {};
842
843 return Ok(quote! {
844 #redacted_display_impl
845 #debug_impl
846 #slog_impl
847 #tracing_impl
848 });
849 }
850
851 let derive_output = match &data {
855 Data::Struct(data) => {
856 let output = derive_struct(&ident, data.clone(), &generics)?;
857 DeriveOutput {
858 redaction_body: output.redaction_body,
859 used_generics: output.used_generics,
860 policy_applicable_generics: output.policy_applicable_generics,
861 debug_redacted_body: output.debug_redacted_body,
862 debug_redacted_generics: output.debug_redacted_generics,
863 debug_unredacted_body: output.debug_unredacted_body,
864 debug_unredacted_generics: output.debug_unredacted_generics,
865 redacted_display_body: None,
866 redacted_display_generics: Vec::new(),
867 redacted_display_debug_generics: Vec::new(),
868 redacted_display_policy_ref_generics: Vec::new(),
869 redacted_display_nested_generics: Vec::new(),
870 }
871 }
872 Data::Enum(data) => {
873 let output = derive_enum(&ident, data.clone(), &generics)?;
874 DeriveOutput {
875 redaction_body: output.redaction_body,
876 used_generics: output.used_generics,
877 policy_applicable_generics: output.policy_applicable_generics,
878 debug_redacted_body: output.debug_redacted_body,
879 debug_redacted_generics: output.debug_redacted_generics,
880 debug_unredacted_body: output.debug_unredacted_body,
881 debug_unredacted_generics: output.debug_unredacted_generics,
882 redacted_display_body: None,
883 redacted_display_generics: Vec::new(),
884 redacted_display_debug_generics: Vec::new(),
885 redacted_display_policy_ref_generics: Vec::new(),
886 redacted_display_nested_generics: Vec::new(),
887 }
888 }
889 Data::Union(u) => {
890 return Err(syn::Error::new(
891 u.union_token.span(),
892 "`Sensitive` cannot be derived for unions",
893 ));
894 }
895 };
896
897 let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
898 let policy_generics =
899 add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
900 let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
901 let debug_redacted_generics =
902 add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
903 let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
904 debug_redacted_generics.split_for_impl();
905 let debug_unredacted_generics =
906 add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
907 let (
908 debug_unredacted_impl_generics,
909 debug_unredacted_ty_generics,
910 debug_unredacted_where_clause,
911 ) = debug_unredacted_generics.split_for_impl();
912 let redaction_body = &derive_output.redaction_body;
913 let debug_redacted_body = &derive_output.debug_redacted_body;
914 let debug_unredacted_body = &derive_output.debug_unredacted_body;
915 let debug_impl = if skip_debug {
916 quote! {}
917 } else {
918 quote! {
919 #[cfg(any(test, feature = "testing"))]
920 impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
921 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
922 #debug_unredacted_body
923 }
924 }
925
926 #[cfg(not(any(test, feature = "testing")))]
927 #[allow(unused_variables)]
928 impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
929 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
930 #debug_redacted_body
931 }
932 }
933 }
934 };
935
936 let redacted_display_body = derive_output.redacted_display_body.as_ref();
937 let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
938 let redacted_display_generics =
939 add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
940 let redacted_display_generics = add_debug_bounds(
941 redacted_display_generics,
942 &derive_output.redacted_display_debug_generics,
943 );
944 let redacted_display_generics = add_policy_applicable_ref_bounds(
945 redacted_display_generics,
946 &derive_output.redacted_display_policy_ref_generics,
947 );
948 let redacted_display_generics = add_redacted_display_bounds(
949 redacted_display_generics,
950 &derive_output.redacted_display_nested_generics,
951 );
952 let (display_impl_generics, display_ty_generics, display_where_clause) =
953 redacted_display_generics.split_for_impl();
954 let redacted_display_body = redacted_display_body
955 .cloned()
956 .unwrap_or_else(TokenStream::new);
957 quote! {
958 impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
959 fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
960 #redacted_display_body
961 }
962 }
963 }
964 } else {
965 quote! {}
966 };
967
968 #[cfg(feature = "slog")]
971 let slog_impl = {
972 let slog_crate = slog_crate()?;
973 let mut slog_generics = generics;
974 let slog_where_clause = slog_generics.make_where_clause();
975 let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
976 match slog_mode {
977 SlogMode::RedactedJson => {
978 slog_where_clause
979 .predicates
980 .push(parse_quote!(#self_ty: ::core::clone::Clone));
981 slog_where_clause
984 .predicates
985 .push(parse_quote!(#self_ty: ::serde::Serialize));
986 slog_where_clause
987 .predicates
988 .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
989 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
990 slog_generics.split_for_impl();
991 quote! {
992 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
993 fn serialize(
994 &self,
995 _record: &#slog_crate::Record<'_>,
996 key: #slog_crate::Key,
997 serializer: &mut dyn #slog_crate::Serializer,
998 ) -> #slog_crate::Result {
999 let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
1000 #slog_crate::Value::serialize(&redacted, _record, key, serializer)
1001 }
1002 }
1003
1004 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
1005 }
1006 }
1007 SlogMode::RedactedDisplay => {
1008 slog_where_clause
1009 .predicates
1010 .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
1011 let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
1012 slog_generics.split_for_impl();
1013 quote! {
1014 impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
1015 fn serialize(
1016 &self,
1017 _record: &#slog_crate::Record<'_>,
1018 key: #slog_crate::Key,
1019 serializer: &mut dyn #slog_crate::Serializer,
1020 ) -> #slog_crate::Result {
1021 let redacted = #crate_root::RedactableDisplay::redacted_display(self);
1022 serializer.emit_arguments(key, &format_args!("{}", redacted))
1023 }
1024 }
1025
1026 impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
1027 }
1028 }
1029 }
1030 };
1031
1032 #[cfg(not(feature = "slog"))]
1033 let slog_impl = quote! {};
1034
1035 #[cfg(feature = "tracing")]
1036 let tracing_impl = quote! {
1037 impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
1038 };
1039
1040 #[cfg(not(feature = "tracing"))]
1041 let tracing_impl = quote! {};
1042
1043 let trait_impl = quote! {
1044 impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
1045 fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
1046 use #crate_root::RedactableContainer as _;
1047 #redaction_body
1048 }
1049 }
1050
1051 #debug_impl
1052
1053 #redacted_display_impl
1054
1055 #slog_impl
1056
1057 #tracing_impl
1058
1059 };
1062 Ok(trait_impl)
1063}
1064
1065fn derive_unredacted_debug(
1066 name: &Ident,
1067 data: &Data,
1068 generics: &syn::Generics,
1069) -> Result<DebugOutput> {
1070 match data {
1071 Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
1072 Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
1073 Data::Union(u) => Err(syn::Error::new(
1074 u.union_token.span(),
1075 "`SensitiveDisplay` cannot be derived for unions",
1076 )),
1077 }
1078}
1079
1080fn derive_unredacted_debug_struct(
1081 name: &Ident,
1082 data: &DataStruct,
1083 generics: &syn::Generics,
1084) -> DebugOutput {
1085 let mut debug_generics = Vec::new();
1086 match &data.fields {
1087 Fields::Named(fields) => {
1088 let mut bindings = Vec::new();
1089 let mut debug_fields = Vec::new();
1090 for field in &fields.named {
1091 let ident = field
1092 .ident
1093 .clone()
1094 .expect("named field should have identifier");
1095 bindings.push(ident.clone());
1096 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1097 debug_fields.push(quote! {
1098 debug.field(stringify!(#ident), #ident);
1099 });
1100 }
1101 DebugOutput {
1102 body: quote! {
1103 match self {
1104 Self { #(#bindings),* } => {
1105 let mut debug = f.debug_struct(stringify!(#name));
1106 #(#debug_fields)*
1107 debug.finish()
1108 }
1109 }
1110 },
1111 generics: debug_generics,
1112 }
1113 }
1114 Fields::Unnamed(fields) => {
1115 let mut bindings = Vec::new();
1116 let mut debug_fields = Vec::new();
1117 for (index, field) in fields.unnamed.iter().enumerate() {
1118 let ident = format_ident!("field_{index}");
1119 bindings.push(ident.clone());
1120 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1121 debug_fields.push(quote! {
1122 debug.field(#ident);
1123 });
1124 }
1125 DebugOutput {
1126 body: quote! {
1127 match self {
1128 Self ( #(#bindings),* ) => {
1129 let mut debug = f.debug_tuple(stringify!(#name));
1130 #(#debug_fields)*
1131 debug.finish()
1132 }
1133 }
1134 },
1135 generics: debug_generics,
1136 }
1137 }
1138 Fields::Unit => DebugOutput {
1139 body: quote! {
1140 f.write_str(stringify!(#name))
1141 },
1142 generics: debug_generics,
1143 },
1144 }
1145}
1146
1147fn derive_unredacted_debug_enum(
1148 name: &Ident,
1149 data: &DataEnum,
1150 generics: &syn::Generics,
1151) -> DebugOutput {
1152 let mut debug_generics = Vec::new();
1153 let mut debug_arms = Vec::new();
1154 for variant in &data.variants {
1155 let variant_ident = &variant.ident;
1156 match &variant.fields {
1157 Fields::Unit => {
1158 debug_arms.push(quote! {
1159 #name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
1160 });
1161 }
1162 Fields::Named(fields) => {
1163 let mut bindings = Vec::new();
1164 let mut debug_fields = Vec::new();
1165 for field in &fields.named {
1166 let ident = field
1167 .ident
1168 .clone()
1169 .expect("named field should have identifier");
1170 bindings.push(ident.clone());
1171 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1172 debug_fields.push(quote! {
1173 debug.field(stringify!(#ident), #ident);
1174 });
1175 }
1176 debug_arms.push(quote! {
1177 #name::#variant_ident { #(#bindings),* } => {
1178 let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
1179 #(#debug_fields)*
1180 debug.finish()
1181 }
1182 });
1183 }
1184 Fields::Unnamed(fields) => {
1185 let mut bindings = Vec::new();
1186 let mut debug_fields = Vec::new();
1187 for (index, field) in fields.unnamed.iter().enumerate() {
1188 let ident = format_ident!("field_{index}");
1189 bindings.push(ident.clone());
1190 collect_generics_from_type(&field.ty, generics, &mut debug_generics);
1191 debug_fields.push(quote! {
1192 debug.field(#ident);
1193 });
1194 }
1195 debug_arms.push(quote! {
1196 #name::#variant_ident ( #(#bindings),* ) => {
1197 let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
1198 #(#debug_fields)*
1199 debug.finish()
1200 }
1201 });
1202 }
1203 }
1204 }
1205 DebugOutput {
1206 body: quote! {
1207 match self {
1208 #(#debug_arms),*
1209 }
1210 },
1211 generics: debug_generics,
1212 }
1213}