use crate::{
flags::{ExtractFlags, FieldFlags, FieldFlagsParse},
redact::UnusedDiagnostic,
};
use quote::ToTokens;
use syn::spanned::Spanned;
#[rustfmt::skip]
fn is_ty_option(ty: &syn::Type) -> bool {
if let syn::Type::Path(syn::TypePath { path, .. }) = &ty {
match path.segments.len() {
1 if path.segments[0].ident == "Option" => true,
3 if (path.segments[0].ident == "std" || path.segments[0].ident == "core") && path.segments[1].ident == "option" && path.segments[2].ident == "Option" => true,
4 if (path.segments[0].ident == "std" || path.segments[0].ident == "core") && path.segments[1].ident == "prelude" && path.segments[3].ident == "Option" => true,
_ => false,
}
} else {
false
}
}
pub(crate) enum FormatData<'a> {
FieldsNamed(&'a syn::FieldsNamed),
FieldsUnnamed(&'a syn::FieldsUnnamed),
}
impl FormatData<'_> {
pub(crate) fn impl_debug(
self,
name: proc_macro2::TokenStream,
all_fields_flags: Option<FieldFlags>,
with_self: bool,
unused: &mut UnusedDiagnostic,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let fields = match self {
Self::FieldsNamed(syn::FieldsNamed { named: fields, .. })
| Self::FieldsUnnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => fields,
};
let mut field_bodies = Vec::with_capacity(fields.len());
for (i, field) in fields.iter().enumerate() {
#[allow(clippy::collapsible_else_if)]
let field_accessor = if with_self {
if let Some(ident) = &field.ident {
quote! { &self.#ident }
} else {
let i = syn::Index::from(i);
quote! { &self.#i }
}
} else {
if let Some(ident) = &field.ident {
ident.into_token_stream()
} else {
syn::Ident::new(&format!("arg{i}"), field.span()).into_token_stream()
}
};
let field_flags = match field.attrs.len() {
0 => all_fields_flags,
1 => match FieldFlags::extract::<1>(
"Redact",
&field.attrs,
FieldFlagsParse {
skip_allowed: all_fields_flags.is_some(),
},
)? {
[Some(flags)] => {
if flags.variant {
return Err(syn::Error::new(
field.attrs[0].span(),
"`#[redact(variant)]` is invalid for structs",
));
} else if flags.all {
return Err(syn::Error::new(
field.attrs[0].span(),
"`#[redact(all)]` is invalid for struct fields",
));
} else {
Some(flags)
}
}
[None] => None,
},
_ => {
return Err(syn::Error::new(
field.span(),
"only one `#[redact(...)]` attribute is allowed per field",
))
}
};
if let Some(field_flags) = field_flags {
let is_option = is_ty_option(&field.ty);
field_bodies.push(generate_redact_call(field_accessor, is_option, &field_flags, unused));
} else {
field_bodies.push(quote! { #field_accessor });
}
}
Ok(match self {
Self::FieldsNamed(syn::FieldsNamed { named, .. }) => {
let field_names = named.iter().map(|field| field.ident.as_ref().unwrap().to_string());
quote! {
fmt.debug_struct(#name)
#(
.field(#field_names, &#field_bodies)
)*
.finish()?
}
}
Self::FieldsUnnamed(syn::FieldsUnnamed { .. }) => {
quote! {
fmt.debug_tuple(#name)
#(
.field(&#field_bodies)
)*
.finish()?
}
}
})
}
}
pub(crate) fn generate_redact_call(
field_accessor: proc_macro2::TokenStream,
is_option: bool,
field_flags: &FieldFlags,
unused: &mut UnusedDiagnostic,
) -> proc_macro2::TokenStream {
if !field_flags.skip {
unused.redacted_something();
let specialization = if is_option {
quote! { ::std::option::Option::Some(::veil::private::RedactSpecialization::Option) }
} else {
quote! { ::std::option::Option::None }
};
if field_flags.display {
quote! {
&::veil::private::RedactionFormatter {
this: ::veil::private::RedactionTarget::Display(#field_accessor),
flags: ::veil::private::RedactFlags { #field_flags },
specialization: #specialization
}
}
} else {
quote! {
&::veil::private::RedactionFormatter {
this: ::veil::private::RedactionTarget::Debug { this: #field_accessor, alternate },
flags: ::veil::private::RedactFlags { #field_flags },
specialization: #specialization
}
}
}
} else {
field_accessor
}
}