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/// - `#[not_sensitive]`: Explicit passthrough - the field is not transformed at all. Use this
116///   for foreign types that don't implement `RedactableContainer`. This is equivalent to wrapping
117///   the field type in `NotSensitiveValue<T>`, but without changing the type signature.
118///
119/// Unions are rejected at compile time.
120///
121/// # Additional Generated Impls
122///
123/// - `Debug`: when *not* building with `cfg(any(test, feature = "testing"))`, sensitive fields are
124///   formatted as the string `"[REDACTED]"` rather than their values. Use `#[sensitive(skip_debug)]`
125///   on the container to opt out.
126/// - `slog::Value` (behind `cfg(feature = "slog")`): implemented by cloning the value and routing
127///   it through `redactable::slog::SlogRedactedExt`. **Note:** this impl requires `Clone` and
128///   `serde::Serialize` because it emits structured JSON. The derive first looks for a top-level
129///   `slog` crate; if not found, it checks the `REDACTABLE_SLOG_CRATE` env var for an alternate path
130///   (e.g., `my_log::slog`). If neither is available, compilation fails with a clear error.
131#[proc_macro_derive(Sensitive, attributes(sensitive, not_sensitive))]
132pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
133    let input = parse_macro_input!(input as DeriveInput);
134    match expand(input, SlogMode::RedactedJson) {
135        Ok(tokens) => tokens.into(),
136        Err(err) => err.into_compile_error().into(),
137    }
138}
139
140/// Derives a no-op `redactable::RedactableContainer` implementation.
141///
142/// This is useful for types that are known to be non-sensitive but still need to
143/// satisfy `RedactableContainer` / `Redactable` bounds.
144#[proc_macro_derive(NotSensitive)]
145pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
146    let input = parse_macro_input!(input as DeriveInput);
147    let ident = input.ident;
148    let generics = input.generics;
149    let attrs = input.attrs;
150    let data = input.data;
151
152    let mut sensitive_attr_spans = Vec::new();
153    if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
154        sensitive_attr_spans.push(attr.span());
155    }
156
157    match &data {
158        Data::Struct(data) => {
159            for field in &data.fields {
160                if field
161                    .attrs
162                    .iter()
163                    .any(|attr| attr.path().is_ident("sensitive"))
164                {
165                    sensitive_attr_spans.push(field.span());
166                }
167            }
168        }
169        Data::Enum(data) => {
170            for variant in &data.variants {
171                for field in &variant.fields {
172                    if field
173                        .attrs
174                        .iter()
175                        .any(|attr| attr.path().is_ident("sensitive"))
176                    {
177                        sensitive_attr_spans.push(field.span());
178                    }
179                }
180            }
181        }
182        Data::Union(data) => {
183            return syn::Error::new(
184                data.union_token.span(),
185                "`NotSensitive` cannot be derived for unions",
186            )
187            .into_compile_error()
188            .into();
189        }
190    }
191
192    if let Some(span) = sensitive_attr_spans.first() {
193        return syn::Error::new(
194            *span,
195            "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
196        )
197        .into_compile_error()
198        .into();
199    }
200    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
201    let crate_root = crate_root();
202
203    let tokens = quote! {
204        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
205            fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
206                self
207            }
208        }
209    };
210    tokens.into()
211}
212
213/// Derives `redactable::RedactableDisplay` using a display template.
214///
215/// This generates a redacted string representation without requiring `Clone`.
216/// Unannotated fields use `RedactableDisplay` by default (passthrough for scalars,
217/// redacted display for nested `SensitiveDisplay` types).
218///
219/// # Field Annotations
220///
221/// - *(none)*: Uses `RedactableDisplay` (requires the field type to implement it)
222/// - `#[sensitive(Policy)]`: Apply the policy's redaction rules
223/// - `#[not_sensitive]`: Render raw via `Display` (use for types without `RedactableDisplay`)
224///
225/// The display template is taken from `#[error("...")]` (thiserror-style) or from
226/// doc comments (displaydoc-style). If neither is present, the derive fails.
227///
228/// Fields are redacted by reference, so field types do not need `Clone`.
229#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
230pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
231    let input = parse_macro_input!(input as DeriveInput);
232    match expand(input, SlogMode::RedactedDisplay) {
233        Ok(tokens) => tokens.into(),
234        Err(err) => err.into_compile_error().into(),
235    }
236}
237
238/// Returns the token stream to reference the redactable crate root.
239///
240/// Handles crate renaming (e.g., `my_redact = { package = "redactable", ... }`)
241/// and internal usage (when derive is used inside the redactable crate itself).
242fn crate_root() -> proc_macro2::TokenStream {
243    match crate_name("redactable") {
244        Ok(FoundCrate::Itself) => quote! { crate },
245        Ok(FoundCrate::Name(name)) => {
246            let ident = format_ident!("{}", name);
247            quote! { ::#ident }
248        }
249        Err(_) => quote! { ::redactable },
250    }
251}
252
253/// Returns the token stream to reference the slog crate root.
254///
255/// Handles crate renaming (e.g., `my_slog = { package = "slog", ... }`).
256/// If the top-level `slog` crate is not available, falls back to the
257/// `REDACTABLE_SLOG_CRATE` env var, which should be a path like `my_log::slog`.
258#[cfg(feature = "slog")]
259fn slog_crate() -> Result<proc_macro2::TokenStream> {
260    match crate_name("slog") {
261        Ok(FoundCrate::Itself) => Ok(quote! { crate }),
262        Ok(FoundCrate::Name(name)) => {
263            let ident = format_ident!("{}", name);
264            Ok(quote! { ::#ident })
265        }
266        Err(_) => {
267            let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
268                syn::Error::new(
269                    Span::call_site(),
270                    "slog support is enabled, but no top-level `slog` crate was found. \
271Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
272`slog` as a direct dependency.",
273                )
274            })?;
275            let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
276                syn::Error::new(
277                    Span::call_site(),
278                    format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
279                )
280            })?;
281            Ok(quote! { #path })
282        }
283    }
284}
285
286fn crate_path(item: &str) -> proc_macro2::TokenStream {
287    let root = crate_root();
288    let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
289    quote! { #root::#item_ident }
290}
291
292struct DeriveOutput {
293    redaction_body: TokenStream,
294    used_generics: Vec<Ident>,
295    policy_applicable_generics: Vec<Ident>,
296    debug_redacted_body: TokenStream,
297    debug_redacted_generics: Vec<Ident>,
298    debug_unredacted_body: TokenStream,
299    debug_unredacted_generics: Vec<Ident>,
300    redacted_display_body: Option<TokenStream>,
301    redacted_display_generics: Vec<Ident>,
302    redacted_display_debug_generics: Vec<Ident>,
303    redacted_display_policy_ref_generics: Vec<Ident>,
304    redacted_display_nested_generics: Vec<Ident>,
305}
306
307enum SlogMode {
308    RedactedJson,
309    RedactedDisplay,
310}
311
312#[allow(clippy::too_many_lines)]
313fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
314    let DeriveInput {
315        ident,
316        generics,
317        data,
318        attrs,
319        ..
320    } = input;
321
322    let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
323
324    let crate_root = crate_root();
325
326    if matches!(slog_mode, SlogMode::RedactedDisplay) {
327        let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
328        let redacted_display_generics =
329            add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
330        let redacted_display_generics = add_debug_bounds(
331            redacted_display_generics,
332            &redacted_display_output.debug_generics,
333        );
334        let redacted_display_generics = add_policy_applicable_ref_bounds(
335            redacted_display_generics,
336            &redacted_display_output.policy_ref_generics,
337        );
338        let redacted_display_generics = add_redacted_display_bounds(
339            redacted_display_generics,
340            &redacted_display_output.nested_generics,
341        );
342        let (display_impl_generics, display_ty_generics, display_where_clause) =
343            redacted_display_generics.split_for_impl();
344        let redacted_display_body = redacted_display_output.body;
345        let redacted_display_impl = quote! {
346            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
347                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
348                    #redacted_display_body
349                }
350            }
351        };
352
353        // Only generate slog impl when the slog feature is enabled on redactable-derive.
354        // If slog is not available, emit a clear error with instructions.
355        #[cfg(feature = "slog")]
356        let slog_impl = {
357            let slog_crate = slog_crate()?;
358            let mut slog_generics = generics;
359            // Get ty_generics first (immutable borrow) before make_where_clause (mutable borrow)
360            let (_, ty_generics, _) = slog_generics.split_for_impl();
361            let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
362            slog_generics
363                .make_where_clause()
364                .predicates
365                .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
366            let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
367                slog_generics.split_for_impl();
368            quote! {
369                impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
370                    fn serialize(
371                        &self,
372                        _record: &#slog_crate::Record<'_>,
373                        key: #slog_crate::Key,
374                        serializer: &mut dyn #slog_crate::Serializer,
375                    ) -> #slog_crate::Result {
376                        let redacted = #crate_root::RedactableDisplay::redacted_display(self);
377                        serializer.emit_arguments(key, &format_args!("{}", redacted))
378                    }
379                }
380
381                impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
382            }
383        };
384
385        #[cfg(not(feature = "slog"))]
386        let slog_impl = quote! {};
387
388        #[cfg(feature = "tracing")]
389        let tracing_impl = {
390            let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
391                redacted_display_generics.split_for_impl();
392            quote! {
393                impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
394            }
395        };
396
397        #[cfg(not(feature = "tracing"))]
398        let tracing_impl = quote! {};
399
400        return Ok(quote! {
401            #redacted_display_impl
402            #slog_impl
403            #tracing_impl
404        });
405    }
406
407    let redacted_display_output = if matches!(slog_mode, SlogMode::RedactedDisplay) {
408        Some(derive_redacted_display(&ident, &data, &attrs, &generics)?)
409    } else {
410        None
411    };
412
413    let derive_output = match &data {
414        Data::Struct(data) => {
415            let output = derive_struct(&ident, data.clone(), &generics)?;
416            DeriveOutput {
417                redaction_body: output.redaction_body,
418                used_generics: output.used_generics,
419                policy_applicable_generics: output.policy_applicable_generics,
420                debug_redacted_body: output.debug_redacted_body,
421                debug_redacted_generics: output.debug_redacted_generics,
422                debug_unredacted_body: output.debug_unredacted_body,
423                debug_unredacted_generics: output.debug_unredacted_generics,
424                redacted_display_body: redacted_display_output
425                    .as_ref()
426                    .map(|output| output.body.clone()),
427                redacted_display_generics: redacted_display_output
428                    .as_ref()
429                    .map(|output| output.display_generics.clone())
430                    .unwrap_or_default(),
431                redacted_display_debug_generics: redacted_display_output
432                    .as_ref()
433                    .map(|output| output.debug_generics.clone())
434                    .unwrap_or_default(),
435                redacted_display_policy_ref_generics: redacted_display_output
436                    .as_ref()
437                    .map(|output| output.policy_ref_generics.clone())
438                    .unwrap_or_default(),
439                redacted_display_nested_generics: redacted_display_output
440                    .as_ref()
441                    .map(|output| output.nested_generics.clone())
442                    .unwrap_or_default(),
443            }
444        }
445        Data::Enum(data) => {
446            let output = derive_enum(&ident, data.clone(), &generics)?;
447            DeriveOutput {
448                redaction_body: output.redaction_body,
449                used_generics: output.used_generics,
450                policy_applicable_generics: output.policy_applicable_generics,
451                debug_redacted_body: output.debug_redacted_body,
452                debug_redacted_generics: output.debug_redacted_generics,
453                debug_unredacted_body: output.debug_unredacted_body,
454                debug_unredacted_generics: output.debug_unredacted_generics,
455                redacted_display_body: redacted_display_output
456                    .as_ref()
457                    .map(|output| output.body.clone()),
458                redacted_display_generics: redacted_display_output
459                    .as_ref()
460                    .map(|output| output.display_generics.clone())
461                    .unwrap_or_default(),
462                redacted_display_debug_generics: redacted_display_output
463                    .as_ref()
464                    .map(|output| output.debug_generics.clone())
465                    .unwrap_or_default(),
466                redacted_display_policy_ref_generics: redacted_display_output
467                    .as_ref()
468                    .map(|output| output.policy_ref_generics.clone())
469                    .unwrap_or_default(),
470                redacted_display_nested_generics: redacted_display_output
471                    .as_ref()
472                    .map(|output| output.nested_generics.clone())
473                    .unwrap_or_default(),
474            }
475        }
476        Data::Union(u) => {
477            return Err(syn::Error::new(
478                u.union_token.span(),
479                "`Sensitive` cannot be derived for unions",
480            ));
481        }
482    };
483
484    let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
485    let policy_generics =
486        add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
487    let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
488    let debug_redacted_generics =
489        add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
490    let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
491        debug_redacted_generics.split_for_impl();
492    let debug_unredacted_generics =
493        add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
494    let (
495        debug_unredacted_impl_generics,
496        debug_unredacted_ty_generics,
497        debug_unredacted_where_clause,
498    ) = debug_unredacted_generics.split_for_impl();
499    let redaction_body = &derive_output.redaction_body;
500    let debug_redacted_body = &derive_output.debug_redacted_body;
501    let debug_unredacted_body = &derive_output.debug_unredacted_body;
502    let debug_impl = if skip_debug {
503        quote! {}
504    } else {
505        quote! {
506            #[cfg(any(test, feature = "testing"))]
507            impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
508                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
509                    #debug_unredacted_body
510                }
511            }
512
513            #[cfg(not(any(test, feature = "testing")))]
514            #[allow(unused_variables)]
515            impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
516                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
517                    #debug_redacted_body
518                }
519            }
520        }
521    };
522
523    let redacted_display_body = derive_output.redacted_display_body.as_ref();
524    let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
525        let redacted_display_generics =
526            add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
527        let redacted_display_generics = add_debug_bounds(
528            redacted_display_generics,
529            &derive_output.redacted_display_debug_generics,
530        );
531        let redacted_display_generics = add_policy_applicable_ref_bounds(
532            redacted_display_generics,
533            &derive_output.redacted_display_policy_ref_generics,
534        );
535        let redacted_display_generics = add_redacted_display_bounds(
536            redacted_display_generics,
537            &derive_output.redacted_display_nested_generics,
538        );
539        let (display_impl_generics, display_ty_generics, display_where_clause) =
540            redacted_display_generics.split_for_impl();
541        let redacted_display_body = redacted_display_body
542            .cloned()
543            .unwrap_or_else(TokenStream::new);
544        quote! {
545            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
546                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
547                    #redacted_display_body
548                }
549            }
550        }
551    } else {
552        quote! {}
553    };
554
555    // Only generate slog impl when the slog feature is enabled on redactable-derive.
556    // If slog is not available, emit a clear error with instructions.
557    #[cfg(feature = "slog")]
558    let slog_impl = {
559        let slog_crate = slog_crate()?;
560        let mut slog_generics = generics;
561        let slog_where_clause = slog_generics.make_where_clause();
562        let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
563        match slog_mode {
564            SlogMode::RedactedJson => {
565                slog_where_clause
566                    .predicates
567                    .push(parse_quote!(#self_ty: ::core::clone::Clone));
568                // SlogRedactedExt requires Self: Serialize, so we add this bound to enable
569                // generic types to work with slog when their type parameters implement Serialize.
570                slog_where_clause
571                    .predicates
572                    .push(parse_quote!(#self_ty: ::serde::Serialize));
573                slog_where_clause
574                    .predicates
575                    .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
576                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
577                    slog_generics.split_for_impl();
578                quote! {
579                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
580                        fn serialize(
581                            &self,
582                            _record: &#slog_crate::Record<'_>,
583                            key: #slog_crate::Key,
584                            serializer: &mut dyn #slog_crate::Serializer,
585                        ) -> #slog_crate::Result {
586                            let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
587                            #slog_crate::Value::serialize(&redacted, _record, key, serializer)
588                        }
589                    }
590
591                    impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
592                }
593            }
594            SlogMode::RedactedDisplay => {
595                slog_where_clause
596                    .predicates
597                    .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
598                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
599                    slog_generics.split_for_impl();
600                quote! {
601                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
602                        fn serialize(
603                            &self,
604                            _record: &#slog_crate::Record<'_>,
605                            key: #slog_crate::Key,
606                            serializer: &mut dyn #slog_crate::Serializer,
607                        ) -> #slog_crate::Result {
608                            let redacted = #crate_root::RedactableDisplay::redacted_display(self);
609                            serializer.emit_arguments(key, &format_args!("{}", redacted))
610                        }
611                    }
612
613                    impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
614                }
615            }
616        }
617    };
618
619    #[cfg(not(feature = "slog"))]
620    let slog_impl = quote! {};
621
622    #[cfg(feature = "tracing")]
623    let tracing_impl = quote! {
624        impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
625    };
626
627    #[cfg(not(feature = "tracing"))]
628    let tracing_impl = quote! {};
629
630    let trait_impl = quote! {
631        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
632            fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
633                use #crate_root::RedactableContainer as _;
634                #redaction_body
635            }
636        }
637
638        #debug_impl
639
640        #redacted_display_impl
641
642        #slog_impl
643
644        #tracing_impl
645
646        // `slog` already provides `impl<V: Value> Value for &V`, so a reference
647        // impl here would conflict with the blanket impl.
648    };
649    Ok(trait_impl)
650}