use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::Result;
use crate::{
crate_path,
generics::collect_generics_from_type,
strategy::Strategy,
types::{is_ip_address_type, is_nonzero_type, is_phantom_data, is_scalar_type},
};
pub(crate) struct DeriveContext<'a> {
pub(crate) generics: &'a syn::Generics,
pub(crate) container_path: &'a TokenStream,
pub(crate) used_generics: &'a mut Vec<Ident>,
pub(crate) policy_applicable_generics: &'a mut Vec<Ident>,
pub(crate) debug_redacted_generics: &'a mut Vec<Ident>,
pub(crate) debug_unredacted_generics: &'a mut Vec<Ident>,
}
fn is_secret_policy(path: &syn::Path) -> bool {
path.is_ident("Secret")
}
fn is_ip_address_policy(path: &syn::Path) -> bool {
path.is_ident("IpAddress")
}
fn nonzero_policy_error(span: Span) -> syn::Error {
syn::Error::new(
span,
"NonZero integer fields cannot use #[sensitive(Policy)] because redaction \
may need to produce zero; use a nullable scalar or a policy-aware wrapper",
)
}
pub(crate) fn generate_field_transform(
ctx: &mut DeriveContext<'_>,
ty: &syn::Type,
binding: &Ident,
span: Span,
strategy: &Strategy,
) -> Result<TokenStream> {
let container_path = ctx.container_path;
match strategy {
Strategy::WalkDefault => {
if is_phantom_data(ty) || is_scalar_type(ty) {
Ok(TokenStream::new())
} else {
collect_generics_from_type(ty, ctx.generics, ctx.used_generics);
collect_generics_from_type(ty, ctx.generics, ctx.debug_redacted_generics);
collect_generics_from_type(ty, ctx.generics, ctx.debug_unredacted_generics);
Ok(quote_spanned! { span =>
let #binding = #container_path::redact_with(#binding, mapper);
})
}
}
Strategy::NotSensitive => {
collect_generics_from_type(ty, ctx.generics, ctx.debug_redacted_generics);
collect_generics_from_type(ty, ctx.generics, ctx.debug_unredacted_generics);
Ok(TokenStream::new())
}
Strategy::Policy(policy_path) => {
if is_nonzero_type(ty) {
return Err(nonzero_policy_error(span));
}
if is_ip_address_type(ty) {
if !is_ip_address_policy(policy_path) {
return Err(syn::Error::new(
span,
"IP address fields can only use #[sensitive(IpAddress)]",
));
}
let policy = policy_path.clone();
let sensitive_with_policy_path = crate_path("SensitiveWithPolicy");
let redaction_policy_path = crate_path("RedactionPolicy");
return Ok(quote_spanned! { span =>
let #binding = <#ty as #sensitive_with_policy_path<#policy>>::redact_with_policy(
#binding,
&<#policy as #redaction_policy_path>::policy(),
);
});
}
if is_scalar_type(ty) {
if is_secret_policy(policy_path) {
Ok(quote_spanned! { span =>
let #binding = mapper.map_scalar(#binding);
})
} else {
Err(syn::Error::new(
span,
"scalar fields can only use #[sensitive(Secret)]; \
other policies are for string-like types",
))
}
} else {
collect_generics_from_type(ty, ctx.generics, ctx.policy_applicable_generics);
collect_generics_from_type(ty, ctx.generics, ctx.debug_unredacted_generics);
let policy = policy_path.clone();
let policy_applicable_path = crate_path("PolicyApplicable");
Ok(quote_spanned! { span =>
let #binding = #policy_applicable_path::apply_policy::<#policy, _>(#binding, mapper);
})
}
}
}
}