Skip to main content

redactable_derive/
lib.rs

1//! Derive macros for `redactable`.
2//!
3//! This crate generates traversal code behind `#[derive(Sensitive)]` and
4//! `#[derive(SensitiveDisplay)]`. It:
5//! - reads `#[sensitive(...)]` field attributes
6//! - emits a `RedactableContainer` implementation that calls into a mapper
7//!
8//! It does **not** define policy markers or text policies. Those live in the main
9//! `redactable` crate and are applied at runtime.
10
11// <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html>
12#![warn(
13    anonymous_parameters,
14    bare_trait_objects,
15    elided_lifetimes_in_paths,
16    missing_copy_implementations,
17    rust_2018_idioms,
18    trivial_casts,
19    trivial_numeric_casts,
20    unreachable_pub,
21    unsafe_code,
22    unused_extern_crates,
23    unused_import_braces
24)]
25// <https://rust-lang.github.io/rust-clippy/stable>
26#![warn(
27    clippy::all,
28    clippy::cargo,
29    clippy::dbg_macro,
30    clippy::float_cmp_const,
31    clippy::get_unwrap,
32    clippy::mem_forget,
33    clippy::nursery,
34    clippy::pedantic,
35    clippy::todo,
36    clippy::unwrap_used,
37    clippy::uninlined_format_args
38)]
39// Allow some clippy lints
40#![allow(
41    clippy::default_trait_access,
42    clippy::doc_markdown,
43    clippy::if_not_else,
44    clippy::module_name_repetitions,
45    clippy::multiple_crate_versions,
46    clippy::must_use_candidate,
47    clippy::needless_pass_by_value,
48    clippy::needless_ifs,
49    clippy::use_self,
50    clippy::cargo_common_metadata,
51    clippy::missing_errors_doc,
52    clippy::enum_glob_use,
53    clippy::struct_excessive_bools,
54    clippy::missing_const_for_fn,
55    clippy::redundant_pub_crate,
56    clippy::result_large_err,
57    clippy::future_not_send,
58    clippy::option_if_let_else,
59    clippy::from_over_into,
60    clippy::manual_inspect
61)]
62// Allow some lints while testing
63#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
64
65#[allow(unused_extern_crates)]
66extern crate proc_macro;
67
68use proc_macro_crate::{FoundCrate, crate_name};
69#[cfg(feature = "slog")]
70use proc_macro2::Span;
71use proc_macro2::{Ident, TokenStream};
72use quote::{format_ident, quote};
73#[cfg(feature = "slog")]
74use syn::parse_quote;
75use syn::{Data, DeriveInput, Result, parse_macro_input, spanned::Spanned};
76
77mod container;
78mod derive_enum;
79mod derive_struct;
80mod generics;
81mod redacted_display;
82mod strategy;
83mod transform;
84mod types;
85use container::{ContainerOptions, parse_container_options};
86use derive_enum::derive_enum;
87use derive_struct::derive_struct;
88use generics::{
89    add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
90    add_policy_applicable_ref_bounds, add_redacted_display_bounds,
91};
92use redacted_display::derive_redacted_display;
93
94/// Derives `redactable::RedactableContainer` (and related impls) for structs and enums.
95///
96/// # Container Attributes
97///
98/// These attributes are placed on the struct/enum itself:
99///
100/// - `#[sensitive(skip_debug)]` - Opt out of `Debug` impl generation. Use this when you need a
101///   custom `Debug` implementation or the type already derives `Debug` elsewhere.
102///
103/// # Field Attributes
104///
105/// - **No annotation**: The field is traversed by default. Scalars pass through unchanged; nested
106///   structs/enums are walked using `RedactableContainer` (so external types must implement it).
107///
108/// - `#[sensitive(Default)]`: For scalar types (i32, bool, char, etc.), redacts to default values
109///   (0, false, '*'). For string-like types, applies full redaction to `"[REDACTED]"`.
110///
111/// - `#[sensitive(Policy)]`: Applies the policy's redaction rules to string-like
112///   values. Works for `String`, `Option<String>`, `Vec<String>`, `Box<String>`. Scalars can only
113///   use `#[sensitive(Default)]`.
114///
115/// Unions are rejected at compile time.
116///
117/// # Additional Generated Impls
118///
119/// - `Debug`: when *not* building with `cfg(any(test, feature = "testing"))`, sensitive fields are
120///   formatted as the string `"[REDACTED]"` rather than their values. Use `#[sensitive(skip_debug)]`
121///   on the container to opt out.
122/// - `slog::Value` (behind `cfg(feature = "slog")`): implemented by cloning the value and routing
123///   it through `redactable::slog::SlogRedactedExt`. **Note:** this impl requires `Clone` and
124///   `serde::Serialize` because it emits structured JSON. The derive first looks for a top-level
125///   `slog` crate; if not found, it checks the `REDACTABLE_SLOG_CRATE` env var for an alternate path
126///   (e.g., `my_log::slog`). If neither is available, compilation fails with a clear error.
127#[proc_macro_derive(Sensitive, attributes(sensitive))]
128pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
129    let input = parse_macro_input!(input as DeriveInput);
130    match expand(input, SlogMode::RedactedJson) {
131        Ok(tokens) => tokens.into(),
132        Err(err) => err.into_compile_error().into(),
133    }
134}
135
136#[proc_macro_derive(SensitiveData, attributes(sensitive))]
137pub fn derive_sensitive_data(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
138    derive_sensitive_container(input)
139}
140
141/// Derives a no-op `redactable::RedactableContainer` implementation.
142///
143/// This is useful for types that are known to be non-sensitive but still need to
144/// satisfy `RedactableContainer` / `Redactable` bounds.
145#[proc_macro_derive(NotSensitive)]
146pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
147    let input = parse_macro_input!(input as DeriveInput);
148    let ident = input.ident;
149    let generics = input.generics;
150    let attrs = input.attrs;
151    let data = input.data;
152
153    let mut sensitive_attr_spans = Vec::new();
154    if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
155        sensitive_attr_spans.push(attr.span());
156    }
157
158    match &data {
159        Data::Struct(data) => {
160            for field in &data.fields {
161                if field
162                    .attrs
163                    .iter()
164                    .any(|attr| attr.path().is_ident("sensitive"))
165                {
166                    sensitive_attr_spans.push(field.span());
167                }
168            }
169        }
170        Data::Enum(data) => {
171            for variant in &data.variants {
172                for field in &variant.fields {
173                    if field
174                        .attrs
175                        .iter()
176                        .any(|attr| attr.path().is_ident("sensitive"))
177                    {
178                        sensitive_attr_spans.push(field.span());
179                    }
180                }
181            }
182        }
183        Data::Union(data) => {
184            return syn::Error::new(
185                data.union_token.span(),
186                "`NotSensitive` cannot be derived for unions",
187            )
188            .into_compile_error()
189            .into();
190        }
191    }
192
193    if let Some(span) = sensitive_attr_spans.first() {
194        return syn::Error::new(
195            *span,
196            "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
197        )
198        .into_compile_error()
199        .into();
200    }
201    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
202    let crate_root = crate_root();
203
204    let tokens = quote! {
205        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
206            fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
207                self
208            }
209        }
210    };
211    tokens.into()
212}
213
214/// Derives `redactable::RedactableDisplay` using a display template.
215///
216/// This generates a redacted string representation without requiring `Clone`.
217/// Unannotated fields use `RedactableDisplay` by default (passthrough for scalars,
218/// redacted display for nested `SensitiveDisplay` types).
219///
220/// # Field Annotations
221///
222/// - *(none)*: Uses `RedactableDisplay` (requires the field type to implement it)
223/// - `#[sensitive(Policy)]`: Apply the policy's redaction rules
224/// - `#[not_sensitive]`: Render raw via `Display` (use for types without `RedactableDisplay`)
225///
226/// The display template is taken from `#[error("...")]` (thiserror-style) or from
227/// doc comments (displaydoc-style). If neither is present, the derive fails.
228///
229/// Fields are redacted by reference, so field types do not need `Clone`.
230#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
231pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
232    let input = parse_macro_input!(input as DeriveInput);
233    match expand(input, SlogMode::RedactedDisplay) {
234        Ok(tokens) => tokens.into(),
235        Err(err) => err.into_compile_error().into(),
236    }
237}
238
239/// Returns the token stream to reference the redactable crate root.
240///
241/// Handles crate renaming (e.g., `my_redact = { package = "redactable", ... }`)
242/// and internal usage (when derive is used inside the redactable crate itself).
243fn crate_root() -> proc_macro2::TokenStream {
244    match crate_name("redactable") {
245        Ok(FoundCrate::Itself) => quote! { crate },
246        Ok(FoundCrate::Name(name)) => {
247            let ident = format_ident!("{}", name);
248            quote! { ::#ident }
249        }
250        Err(_) => quote! { ::redactable },
251    }
252}
253
254/// Returns the token stream to reference the slog crate root.
255///
256/// Handles crate renaming (e.g., `my_slog = { package = "slog", ... }`).
257/// If the top-level `slog` crate is not available, falls back to the
258/// `REDACTABLE_SLOG_CRATE` env var, which should be a path like `my_log::slog`.
259#[cfg(feature = "slog")]
260fn slog_crate() -> Result<proc_macro2::TokenStream> {
261    match crate_name("slog") {
262        Ok(FoundCrate::Itself) => Ok(quote! { crate }),
263        Ok(FoundCrate::Name(name)) => {
264            let ident = format_ident!("{}", name);
265            Ok(quote! { ::#ident })
266        }
267        Err(_) => {
268            let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
269                syn::Error::new(
270                    Span::call_site(),
271                    "slog support is enabled, but no top-level `slog` crate was found. \
272Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
273`slog` as a direct dependency.",
274                )
275            })?;
276            let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
277                syn::Error::new(
278                    Span::call_site(),
279                    format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
280                )
281            })?;
282            Ok(quote! { #path })
283        }
284    }
285}
286
287fn crate_path(item: &str) -> proc_macro2::TokenStream {
288    let root = crate_root();
289    let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
290    quote! { #root::#item_ident }
291}
292
293struct DeriveOutput {
294    redaction_body: TokenStream,
295    used_generics: Vec<Ident>,
296    policy_applicable_generics: Vec<Ident>,
297    debug_redacted_body: TokenStream,
298    debug_redacted_generics: Vec<Ident>,
299    debug_unredacted_body: TokenStream,
300    debug_unredacted_generics: Vec<Ident>,
301    redacted_display_body: Option<TokenStream>,
302    redacted_display_generics: Vec<Ident>,
303    redacted_display_debug_generics: Vec<Ident>,
304    redacted_display_policy_ref_generics: Vec<Ident>,
305    redacted_display_nested_generics: Vec<Ident>,
306}
307
308enum SlogMode {
309    RedactedJson,
310    RedactedDisplay,
311}
312
313#[allow(clippy::too_many_lines)]
314fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
315    let DeriveInput {
316        ident,
317        generics,
318        data,
319        attrs,
320        ..
321    } = input;
322
323    let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
324
325    let crate_root = crate_root();
326
327    if matches!(slog_mode, SlogMode::RedactedDisplay) {
328        let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
329        let redacted_display_generics =
330            add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
331        let redacted_display_generics = add_debug_bounds(
332            redacted_display_generics,
333            &redacted_display_output.debug_generics,
334        );
335        let redacted_display_generics = add_policy_applicable_ref_bounds(
336            redacted_display_generics,
337            &redacted_display_output.policy_ref_generics,
338        );
339        let redacted_display_generics = add_redacted_display_bounds(
340            redacted_display_generics,
341            &redacted_display_output.nested_generics,
342        );
343        let (display_impl_generics, display_ty_generics, display_where_clause) =
344            redacted_display_generics.split_for_impl();
345        let redacted_display_body = redacted_display_output.body;
346        let redacted_display_impl = quote! {
347            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
348                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
349                    #redacted_display_body
350                }
351            }
352        };
353
354        // Only generate slog impl when the slog feature is enabled on redactable-derive.
355        // If slog is not available, emit a clear error with instructions.
356        #[cfg(feature = "slog")]
357        let slog_impl = {
358            let slog_crate = slog_crate()?;
359            let mut slog_generics = generics;
360            // Get ty_generics first (immutable borrow) before make_where_clause (mutable borrow)
361            let (_, ty_generics, _) = slog_generics.split_for_impl();
362            let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
363            slog_generics
364                .make_where_clause()
365                .predicates
366                .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
367            let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
368                slog_generics.split_for_impl();
369            quote! {
370                impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
371                    fn serialize(
372                        &self,
373                        _record: &#slog_crate::Record<'_>,
374                        key: #slog_crate::Key,
375                        serializer: &mut dyn #slog_crate::Serializer,
376                    ) -> #slog_crate::Result {
377                        let redacted = #crate_root::RedactableDisplay::redacted_display(self);
378                        serializer.emit_arguments(key, &format_args!("{}", redacted))
379                    }
380                }
381
382                impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
383            }
384        };
385
386        #[cfg(not(feature = "slog"))]
387        let slog_impl = quote! {};
388
389        #[cfg(feature = "tracing")]
390        let tracing_impl = {
391            let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
392                redacted_display_generics.split_for_impl();
393            quote! {
394                impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
395            }
396        };
397
398        #[cfg(not(feature = "tracing"))]
399        let tracing_impl = quote! {};
400
401        return Ok(quote! {
402            #redacted_display_impl
403            #slog_impl
404            #tracing_impl
405        });
406    }
407
408    let redacted_display_output = if matches!(slog_mode, SlogMode::RedactedDisplay) {
409        Some(derive_redacted_display(&ident, &data, &attrs, &generics)?)
410    } else {
411        None
412    };
413
414    let derive_output = match &data {
415        Data::Struct(data) => {
416            let output = derive_struct(&ident, data.clone(), &generics)?;
417            DeriveOutput {
418                redaction_body: output.redaction_body,
419                used_generics: output.used_generics,
420                policy_applicable_generics: output.policy_applicable_generics,
421                debug_redacted_body: output.debug_redacted_body,
422                debug_redacted_generics: output.debug_redacted_generics,
423                debug_unredacted_body: output.debug_unredacted_body,
424                debug_unredacted_generics: output.debug_unredacted_generics,
425                redacted_display_body: redacted_display_output
426                    .as_ref()
427                    .map(|output| output.body.clone()),
428                redacted_display_generics: redacted_display_output
429                    .as_ref()
430                    .map(|output| output.display_generics.clone())
431                    .unwrap_or_default(),
432                redacted_display_debug_generics: redacted_display_output
433                    .as_ref()
434                    .map(|output| output.debug_generics.clone())
435                    .unwrap_or_default(),
436                redacted_display_policy_ref_generics: redacted_display_output
437                    .as_ref()
438                    .map(|output| output.policy_ref_generics.clone())
439                    .unwrap_or_default(),
440                redacted_display_nested_generics: redacted_display_output
441                    .as_ref()
442                    .map(|output| output.nested_generics.clone())
443                    .unwrap_or_default(),
444            }
445        }
446        Data::Enum(data) => {
447            let output = derive_enum(&ident, data.clone(), &generics)?;
448            DeriveOutput {
449                redaction_body: output.redaction_body,
450                used_generics: output.used_generics,
451                policy_applicable_generics: output.policy_applicable_generics,
452                debug_redacted_body: output.debug_redacted_body,
453                debug_redacted_generics: output.debug_redacted_generics,
454                debug_unredacted_body: output.debug_unredacted_body,
455                debug_unredacted_generics: output.debug_unredacted_generics,
456                redacted_display_body: redacted_display_output
457                    .as_ref()
458                    .map(|output| output.body.clone()),
459                redacted_display_generics: redacted_display_output
460                    .as_ref()
461                    .map(|output| output.display_generics.clone())
462                    .unwrap_or_default(),
463                redacted_display_debug_generics: redacted_display_output
464                    .as_ref()
465                    .map(|output| output.debug_generics.clone())
466                    .unwrap_or_default(),
467                redacted_display_policy_ref_generics: redacted_display_output
468                    .as_ref()
469                    .map(|output| output.policy_ref_generics.clone())
470                    .unwrap_or_default(),
471                redacted_display_nested_generics: redacted_display_output
472                    .as_ref()
473                    .map(|output| output.nested_generics.clone())
474                    .unwrap_or_default(),
475            }
476        }
477        Data::Union(u) => {
478            return Err(syn::Error::new(
479                u.union_token.span(),
480                "`Sensitive` cannot be derived for unions",
481            ));
482        }
483    };
484
485    let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
486    let policy_generics =
487        add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
488    let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
489    let debug_redacted_generics =
490        add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
491    let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
492        debug_redacted_generics.split_for_impl();
493    let debug_unredacted_generics =
494        add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
495    let (
496        debug_unredacted_impl_generics,
497        debug_unredacted_ty_generics,
498        debug_unredacted_where_clause,
499    ) = debug_unredacted_generics.split_for_impl();
500    let redaction_body = &derive_output.redaction_body;
501    let debug_redacted_body = &derive_output.debug_redacted_body;
502    let debug_unredacted_body = &derive_output.debug_unredacted_body;
503    let debug_impl = if skip_debug {
504        quote! {}
505    } else {
506        quote! {
507            #[cfg(any(test, feature = "testing"))]
508            impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
509                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
510                    #debug_unredacted_body
511                }
512            }
513
514            #[cfg(not(any(test, feature = "testing")))]
515            #[allow(unused_variables)]
516            impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
517                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
518                    #debug_redacted_body
519                }
520            }
521        }
522    };
523
524    let redacted_display_body = derive_output.redacted_display_body.as_ref();
525    let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
526        let redacted_display_generics =
527            add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
528        let redacted_display_generics = add_debug_bounds(
529            redacted_display_generics,
530            &derive_output.redacted_display_debug_generics,
531        );
532        let redacted_display_generics = add_policy_applicable_ref_bounds(
533            redacted_display_generics,
534            &derive_output.redacted_display_policy_ref_generics,
535        );
536        let redacted_display_generics = add_redacted_display_bounds(
537            redacted_display_generics,
538            &derive_output.redacted_display_nested_generics,
539        );
540        let (display_impl_generics, display_ty_generics, display_where_clause) =
541            redacted_display_generics.split_for_impl();
542        let redacted_display_body = redacted_display_body
543            .cloned()
544            .unwrap_or_else(TokenStream::new);
545        quote! {
546            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
547                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
548                    #redacted_display_body
549                }
550            }
551        }
552    } else {
553        quote! {}
554    };
555
556    // Only generate slog impl when the slog feature is enabled on redactable-derive.
557    // If slog is not available, emit a clear error with instructions.
558    #[cfg(feature = "slog")]
559    let slog_impl = {
560        let slog_crate = slog_crate()?;
561        let mut slog_generics = generics;
562        let slog_where_clause = slog_generics.make_where_clause();
563        let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
564        match slog_mode {
565            SlogMode::RedactedJson => {
566                slog_where_clause
567                    .predicates
568                    .push(parse_quote!(#self_ty: ::core::clone::Clone));
569                // SlogRedactedExt requires Self: Serialize, so we add this bound to enable
570                // generic types to work with slog when their type parameters implement Serialize.
571                slog_where_clause
572                    .predicates
573                    .push(parse_quote!(#self_ty: ::serde::Serialize));
574                slog_where_clause
575                    .predicates
576                    .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
577                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
578                    slog_generics.split_for_impl();
579                quote! {
580                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
581                        fn serialize(
582                            &self,
583                            _record: &#slog_crate::Record<'_>,
584                            key: #slog_crate::Key,
585                            serializer: &mut dyn #slog_crate::Serializer,
586                        ) -> #slog_crate::Result {
587                            let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
588                            #slog_crate::Value::serialize(&redacted, _record, key, serializer)
589                        }
590                    }
591
592                    impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
593                }
594            }
595            SlogMode::RedactedDisplay => {
596                slog_where_clause
597                    .predicates
598                    .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
599                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
600                    slog_generics.split_for_impl();
601                quote! {
602                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
603                        fn serialize(
604                            &self,
605                            _record: &#slog_crate::Record<'_>,
606                            key: #slog_crate::Key,
607                            serializer: &mut dyn #slog_crate::Serializer,
608                        ) -> #slog_crate::Result {
609                            let redacted = #crate_root::RedactableDisplay::redacted_display(self);
610                            serializer.emit_arguments(key, &format_args!("{}", redacted))
611                        }
612                    }
613
614                    impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
615                }
616            }
617        }
618    };
619
620    #[cfg(not(feature = "slog"))]
621    let slog_impl = quote! {};
622
623    #[cfg(feature = "tracing")]
624    let tracing_impl = quote! {
625        impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
626    };
627
628    #[cfg(not(feature = "tracing"))]
629    let tracing_impl = quote! {};
630
631    let trait_impl = quote! {
632        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
633            fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
634                use #crate_root::RedactableContainer as _;
635                #redaction_body
636            }
637        }
638
639        #debug_impl
640
641        #redacted_display_impl
642
643        #slog_impl
644
645        #tracing_impl
646
647        // `slog` already provides `impl<V: Value> Value for &V`, so a reference
648        // impl here would conflict with the blanket impl.
649    };
650    Ok(trait_impl)
651}