use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Data, DeriveInput, Result, parse_quote, spanned::Spanned};
use crate::{
crate_paths::{crate_root, isolate_generated_items},
fresh_ident::FreshIdentAllocator,
};
fn reject_sensitivity_attrs(attrs: &[syn::Attribute], data: &Data, macro_name: &str) -> Result<()> {
let check_attr = |attr: &syn::Attribute| -> Result<()> {
if attr.path().is_ident("sensitive") {
return Err(syn::Error::new(
attr.span(),
format!("`#[sensitive]` attributes are not allowed on `{macro_name}` types"),
));
}
if attr.path().is_ident("not_sensitive") {
return Err(syn::Error::new(
attr.span(),
format!(
"`#[not_sensitive]` attributes are not needed on `{macro_name}` types (the entire type is already non-sensitive)"
),
));
}
Ok(())
};
for attr in attrs {
check_attr(attr)?;
}
match data {
Data::Struct(data) => {
for field in &data.fields {
for attr in &field.attrs {
check_attr(attr)?;
}
}
}
Data::Enum(data) => {
for variant in &data.variants {
for attr in &variant.attrs {
check_attr(attr)?;
}
for field in &variant.fields {
for attr in &field.attrs {
check_attr(attr)?;
}
}
}
}
Data::Union(_) => {}
}
Ok(())
}
pub(crate) fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
let mut fresh = FreshIdentAllocator::new(&input);
let DeriveInput {
ident,
generics,
data,
attrs,
..
} = input;
if let Data::Union(u) = &data {
return Err(syn::Error::new(
u.union_token.span(),
"`NotSensitive` cannot be derived for unions",
));
}
reject_sensitivity_attrs(&attrs, &data, "NotSensitive")?;
let crate_root = crate_root();
let mapper_type = fresh.fresh("__RedactableMapper");
let mapper = fresh.fresh("__redactable_mapper");
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let container_impl = quote! {
impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
fn redact_with<#mapper_type: #crate_root::RedactableMapper>(self, #mapper: &#mapper_type) -> Self {
self
}
}
impl #impl_generics #crate_root::Redactable for #ident #ty_generics #where_clause {}
};
#[cfg(feature = "slog")]
let slog_impl = {
let record = fresh.fresh("__redactable_record");
let key = fresh.fresh("key");
let serializer = fresh.fresh("serializer");
let slog_crate = quote! { #crate_root::__private::slog };
let mut slog_generics = generics.clone();
let (_, ty_generics, _) = slog_generics.split_for_impl();
let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
slog_generics
.make_where_clause()
.predicates
.push(parse_quote!(#self_ty: #crate_root::__private::serde::Serialize));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
#record: &#slog_crate::Record<'_>,
#key: #slog_crate::Key,
#serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
#crate_root::slog::__slog_serialize_not_sensitive(self, #record, #key, #serializer)
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
};
#[cfg(not(feature = "slog"))]
let slog_impl = quote! {};
#[cfg(feature = "tracing")]
let tracing_impl = {
let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
generics.split_for_impl();
quote! {
impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
}
};
#[cfg(not(feature = "tracing"))]
let tracing_impl = quote! {};
let generated = quote! {
#container_impl
#slog_impl
#tracing_impl
};
Ok(isolate_generated_items(generated, &fresh))
}
pub(crate) fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
let mut fresh = FreshIdentAllocator::new(&input);
let DeriveInput {
ident,
generics,
data,
attrs,
..
} = input;
if let Data::Union(u) = &data {
return Err(syn::Error::new(
u.union_token.span(),
"`NotSensitiveDisplay` cannot be derived for unions",
));
}
reject_sensitivity_attrs(&attrs, &data, "NotSensitiveDisplay")?;
let crate_root = crate_root();
let mapper_type = fresh.fresh("__RedactableMapper");
let mapper = fresh.fresh("__redactable_mapper");
let formatter = fresh.fresh("__redactable_f");
let (container_impl, redacted_display_impl) = not_sensitive_display_core_impls(
&ident,
&generics,
&crate_root,
&mapper_type,
&mapper,
&formatter,
);
#[cfg(feature = "tracing")]
let tracing_impl = {
let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
generics.split_for_impl();
quote! {
impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
}
};
#[cfg(not(feature = "tracing"))]
let tracing_impl = quote! {};
#[cfg(feature = "slog")]
let slog_impl = {
let record = fresh.fresh("__redactable_record");
let key = fresh.fresh("key");
let serializer = fresh.fresh("serializer");
let redacted = fresh.fresh("__redactable_value");
let slog_crate = quote! { #crate_root::__private::slog };
let mut slog_generics = generics;
let (_, ty_generics, _) = slog_generics.split_for_impl();
let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
slog_generics
.make_where_clause()
.predicates
.push(syn::parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
#record: &#slog_crate::Record<'_>,
#key: #slog_crate::Key,
#serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
let #redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
#serializer.emit_arguments(#key, &format_args!("{}", #redacted))
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
};
#[cfg(not(feature = "slog"))]
let slog_impl = quote! {};
let generated = quote! {
#container_impl
#redacted_display_impl
#slog_impl
#tracing_impl
};
Ok(isolate_generated_items(generated, &fresh))
}
fn not_sensitive_display_core_impls(
ident: &Ident,
generics: &syn::Generics,
crate_root: &TokenStream,
mapper_type: &Ident,
mapper: &Ident,
formatter: &Ident,
) -> (TokenStream, TokenStream) {
let (container_impl_generics, container_ty_generics, container_where_clause) =
generics.split_for_impl();
let container_impl = quote! {
impl #container_impl_generics #crate_root::RedactableWithMapper for #ident #container_ty_generics #container_where_clause {
fn redact_with<#mapper_type: #crate_root::RedactableMapper>(self, #mapper: &#mapper_type) -> Self {
self
}
}
impl #container_impl_generics #crate_root::Redactable for #ident #container_ty_generics #container_where_clause {}
};
let mut display_generics = generics.clone();
let (_, display_ty, _) = generics.split_for_impl();
let display_self_ty: syn::Type = parse_quote!(#ident #display_ty);
display_generics
.make_where_clause()
.predicates
.push(parse_quote!(#display_self_ty: ::core::fmt::Display));
let (display_impl_generics, display_ty_generics, display_where_clause) =
display_generics.split_for_impl();
let redacted_display_impl = quote! {
impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
fn fmt_redacted(&self, #formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(self, #formatter)
}
}
impl #display_impl_generics #crate_root::ToRedactedOutput for #ident #display_ty_generics #display_where_clause {
fn to_redacted_output(&self) -> #crate_root::RedactedOutput {
#crate_root::RedactedOutput::Text(
::std::string::ToString::to_string(
&#crate_root::RedactableWithFormatter::redacted_display(self),
),
)
}
}
};
(container_impl, redacted_display_impl)
}