1#![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#![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(
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#![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#[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#[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#[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
238fn 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#[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 #[cfg(feature = "slog")]
356 let slog_impl = {
357 let slog_crate = slog_crate()?;
358 let mut slog_generics = generics;
359 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 #[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 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 };
649 Ok(trait_impl)
650}