use proc_macro2::TokenStream;
use quote::{
format_ident,
quote,
quote_spanned,
};
use syn::{
DeriveInput,
Path,
spanned::Spanned,
};
use crate::{
field_assertion,
field_mode::FieldMode,
generic_bounds,
internal::{
ContainerData,
FieldsData,
NamedField,
UnnamedField,
VariantData,
},
};
pub(crate) fn expand(
input: &DeriveInput,
runtime: &Path,
model: &ContainerData<'_>,
) -> syn::Result<TokenStream> {
let mut redaction_generics = input.generics.clone();
generic_bounds::add_mutable_bounds(&mut redaction_generics, model, runtime);
let (mutable_assertions, mutations) = match &model {
ContainerData::Struct(fields) => (
mutable_assertions(&input.ident, fields, runtime),
mutations(&input.ident, fields, runtime),
),
ContainerData::Enum(variants) => (
enum_mutable_assertions(&input.ident, variants, runtime),
enum_mutations(&input.ident, variants, runtime),
),
};
let name = &input.ident;
let (impl_generics, type_generics, where_clause) =
redaction_generics.split_for_impl();
Ok(quote! {
impl #impl_generics #runtime::RedactMut for #name #type_generics #where_clause {
fn redact_in_place_with(&mut self, policy: &#runtime::RedactionPolicy) {
let _ = policy;
#(#mutable_assertions)*
#mutations
}
}
})
}
fn mutable_assertions(
type_name: &syn::Ident,
fields: &FieldsData<'_>,
runtime: &Path,
) -> Vec<TokenStream> {
match fields {
FieldsData::Named(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.identifier().to_string();
field_assertion::mutable(
type_name,
parsed.field(),
&field_name,
parsed.attributes().mode(),
runtime,
)
})
.collect(),
FieldsData::Unnamed(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.index().index.to_string();
field_assertion::mutable(
type_name,
parsed.field(),
&field_name,
parsed.attributes().mode(),
runtime,
)
})
.collect(),
FieldsData::Unit => Vec::new(),
}
}
fn mutations(
type_name: &syn::Ident,
fields: &FieldsData<'_>,
runtime: &Path,
) -> TokenStream {
match fields {
FieldsData::Named(fields) => {
let mutations = named_mutations(type_name, fields, runtime);
quote!(#(#mutations)*)
}
FieldsData::Unnamed(fields) => {
let mutations = unnamed_mutations(type_name, fields, runtime);
quote!(#(#mutations)*)
}
FieldsData::Unit => TokenStream::new(),
}
}
fn named_mutations(
type_name: &syn::Ident,
fields: &[NamedField<'_>],
runtime: &Path,
) -> Vec<TokenStream> {
fields
.iter()
.filter_map(|parsed| {
let field = parsed.field();
let identifier = parsed.identifier();
let mode = parsed.attributes().mode();
if matches!(mode, FieldMode::Plain | FieldMode::Skip) {
return None;
}
let field_name = identifier.to_string();
let helper = field_assertion::helper_name(
type_name,
field,
&field_name,
mutable_trait_name(mode),
);
let invocation = if matches!(mode, FieldMode::Json) {
quote!(#runtime::__qubit_redact_json!(#helper(&mut self.#identifier, policy);))
} else {
quote!(#helper(&mut self.#identifier, policy);)
};
Some(quote_spanned! {field.span()=> #invocation })
})
.collect()
}
fn unnamed_mutations(
type_name: &syn::Ident,
fields: &[UnnamedField<'_>],
runtime: &Path,
) -> Vec<TokenStream> {
fields
.iter()
.filter_map(|parsed| {
let field = parsed.field();
let index = parsed.index();
let mode = parsed.attributes().mode();
if matches!(mode, FieldMode::Plain | FieldMode::Skip) {
return None;
}
let field_name = index.index.to_string();
let helper = field_assertion::helper_name(
type_name,
field,
&field_name,
mutable_trait_name(mode),
);
let invocation = if matches!(mode, FieldMode::Json) {
quote!(#runtime::__qubit_redact_json!(#helper(&mut self.#index, policy);))
} else {
quote!(#helper(&mut self.#index, policy);)
};
Some(quote_spanned! {field.span()=> #invocation })
})
.collect()
}
const fn mutable_trait_name(mode: &FieldMode) -> &'static str {
match mode {
FieldMode::Level(_) => "RedactValueMut",
FieldMode::Nested => "RedactMut",
FieldMode::Map => "RedactMapValueMut",
FieldMode::Json => "Json",
FieldMode::Plain | FieldMode::Skip => "Unused",
}
}
fn enum_mutable_assertions(
type_name: &syn::Ident,
variants: &[VariantData<'_>],
runtime: &Path,
) -> Vec<TokenStream> {
variants
.iter()
.flat_map(|variant| {
let variant_name = &variant.variant().ident;
match variant.fields() {
FieldsData::Named(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.identifier().to_string();
let context = variant_field_context(
variant.index(),
variant_name,
&field_name,
);
field_assertion::mutable(
type_name,
parsed.field(),
&context,
parsed.attributes().mode(),
runtime,
)
})
.collect::<Vec<_>>(),
FieldsData::Unnamed(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.index().index.to_string();
let context = variant_field_context(
variant.index(),
variant_name,
&field_name,
);
field_assertion::mutable(
type_name,
parsed.field(),
&context,
parsed.attributes().mode(),
runtime,
)
})
.collect(),
FieldsData::Unit => Vec::new(),
}
})
.collect()
}
fn enum_mutations(
type_name: &syn::Ident,
variants: &[VariantData<'_>],
runtime: &Path,
) -> TokenStream {
let arms = variants.iter().map(|variant| {
let variant_name = &variant.variant().ident;
match variant.fields() {
FieldsData::Named(fields) => enum_named_mutation_arm(
type_name,
variant.index(),
variant_name,
fields,
runtime,
),
FieldsData::Unnamed(fields) => enum_unnamed_mutation_arm(
type_name,
variant.index(),
variant_name,
fields,
runtime,
),
FieldsData::Unit => quote!(Self::#variant_name => {}),
}
});
quote! {
match self {
#(#arms),*
}
}
}
fn enum_named_mutation_arm(
type_name: &syn::Ident,
variant_index: u32,
variant_name: &syn::Ident,
fields: &[NamedField<'_>],
runtime: &Path,
) -> TokenStream {
let patterns = fields.iter().map(|parsed| {
let identifier = parsed.identifier();
if matches!(
parsed.attributes().mode(),
FieldMode::Level(_)
| FieldMode::Nested
| FieldMode::Map
| FieldMode::Json,
) {
quote!(#identifier)
} else {
quote!(#identifier: _)
}
});
let mutations = fields.iter().filter_map(|parsed| {
let field = parsed.field();
let identifier = parsed.identifier();
let mode = parsed.attributes().mode();
if matches!(mode, FieldMode::Plain | FieldMode::Skip) {
return None;
}
let field_name = identifier.to_string();
let context =
variant_field_context(variant_index, variant_name, &field_name);
let helper =
field_assertion::helper_name(type_name, field, &context, mutable_trait_name(mode));
let invocation = if matches!(mode, FieldMode::Json) {
quote!(#runtime::__qubit_redact_json!(#helper(#identifier, policy);))
} else {
quote!(#helper(#identifier, policy);)
};
Some(quote_spanned! {field.span()=> #invocation })
});
quote! {
Self::#variant_name { #(#patterns),* } => {
#(#mutations)*
}
}
}
fn enum_unnamed_mutation_arm(
type_name: &syn::Ident,
variant_index: u32,
variant_name: &syn::Ident,
fields: &[UnnamedField<'_>],
runtime: &Path,
) -> TokenStream {
let bindings = fields
.iter()
.map(|parsed| {
format_ident!(
"__qubit_redact_field_{}",
parsed.index().index,
span = parsed.field().span(),
)
})
.collect::<Vec<_>>();
let patterns = fields.iter().zip(&bindings).map(|(parsed, binding)| {
if matches!(
parsed.attributes().mode(),
FieldMode::Level(_)
| FieldMode::Nested
| FieldMode::Map
| FieldMode::Json,
) {
quote!(#binding)
} else {
quote!(_)
}
});
let mutations = fields
.iter()
.zip(&bindings)
.filter_map(|(parsed, binding)| {
let field = parsed.field();
let mode = parsed.attributes().mode();
if matches!(mode, FieldMode::Plain | FieldMode::Skip) {
return None;
}
let field_name = parsed.index().index.to_string();
let context =
variant_field_context(variant_index, variant_name, &field_name);
let helper =
field_assertion::helper_name(type_name, field, &context, mutable_trait_name(mode));
let invocation = if matches!(mode, FieldMode::Json) {
quote!(#runtime::__qubit_redact_json!(#helper(#binding, policy);))
} else {
quote!(#helper(#binding, policy);)
};
Some(quote_spanned! {field.span()=> #invocation })
});
quote! {
Self::#variant_name(#(#patterns),*) => {
#(#mutations)*
}
}
}
#[inline]
fn variant_field_context(
variant_index: u32,
variant_name: &syn::Ident,
field_name: &str,
) -> String {
format!("{variant_name}_{variant_index}_{field_name}")
}