use proc_macro2::TokenStream;
use quote::{
format_ident,
quote_spanned,
};
use syn::{
Field,
GenericParam,
Ident,
LifetimeParam,
Path,
spanned::Spanned,
};
use crate::{
field_mode::FieldMode,
generic_bounds,
immutable_trait_name::ImmutableTraitName,
};
pub(crate) fn immutable(
type_name: &Ident,
field: &Field,
field_name: &str,
mode: &FieldMode,
runtime: &Path,
) -> TokenStream {
let helper =
helper_name(type_name, field, field_name, mode.immutable_trait_name());
match mode {
FieldMode::Plain | FieldMode::Skip => TokenStream::new(),
FieldMode::Level(sensitivity) => {
let level = sensitivity.runtime_tokens(runtime);
quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[allow(clippy::ptr_arg)]
#[inline(always)]
fn #helper<'a, 's, 'p, __QubitRedactField>(
value: &'a __QubitRedactField,
session: &'s #runtime::RedactionSession<'p>,
) -> #runtime::RedactedValue<'a>
where
__QubitRedactField: #runtime::RedactValue + ?Sized,
{
#runtime::RedactValue::redact_value(
value,
#level,
session.policy().masking(),
)
}
}
}
FieldMode::Nested => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<'a, 's, 'p, __QubitRedactField>(
value: &'a __QubitRedactField,
session: &'s #runtime::RedactionSession<'p>,
) -> #runtime::RedactedSessionView<
'a,
's,
'p,
__QubitRedactField,
>
where
__QubitRedactField: #runtime::Redact,
{
#runtime::RedactedSessionView::new(value, session)
}
},
FieldMode::Map => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<
'a,
's,
'p,
__QubitRedactField,
__QubitRedactKey: ?Sized,
__QubitRedactValue: ?Sized,
>(
value: &'a __QubitRedactField,
session: &'s #runtime::RedactionSession<'p>,
) -> #runtime::RedactedMapSession<
'a,
's,
'p,
__QubitRedactField,
__QubitRedactKey,
__QubitRedactValue,
>
where
__QubitRedactField:
#runtime::RedactMapValue<
__QubitRedactKey,
__QubitRedactValue,
> + ?Sized,
{
#runtime::RedactedMapSession::new(value, session)
}
},
FieldMode::Json => quote_spanned! {field.span()=>
#runtime::__qubit_redact_json! {
#[allow(non_snake_case)]
#[allow(clippy::ptr_arg)]
#[inline(always)]
fn #helper<'a, 's, 'p>(
value: &'a ::std::string::String,
session: &'s #runtime::RedactionSession<'p>,
) -> #runtime::RedactedJsonTextSession<'a, 's, 'p> {
#runtime::RedactedJsonTextSession::new(value, session)
}
}
},
}
}
pub(crate) fn mutable(
type_name: &Ident,
field: &Field,
field_name: &str,
mode: &FieldMode,
runtime: &Path,
) -> TokenStream {
let helper =
helper_name(type_name, field, field_name, mode.mutable_trait_name());
match mode {
FieldMode::Plain | FieldMode::Skip => TokenStream::new(),
FieldMode::Level(sensitivity) => {
let level = sensitivity.runtime_tokens(runtime);
quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<__QubitRedactField>(
value: &mut __QubitRedactField,
policy: &#runtime::RedactionPolicy,
)
where
__QubitRedactField: #runtime::RedactValueMut + ?Sized,
{
#runtime::RedactValueMut::redact_value_in_place(
value,
#level,
policy.masking(),
);
}
}
}
FieldMode::Nested => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<__QubitRedactField>(
value: &mut __QubitRedactField,
policy: &#runtime::RedactionPolicy,
)
where
__QubitRedactField: #runtime::RedactMut + ?Sized,
{
#runtime::RedactMut::redact_in_place_with(value, policy);
}
},
FieldMode::Map => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<
__QubitRedactField,
__QubitRedactKey: ?Sized,
__QubitRedactValue: ?Sized,
>(
value: &mut __QubitRedactField,
policy: &#runtime::RedactionPolicy,
)
where
__QubitRedactField:
#runtime::RedactMapValueMut<
__QubitRedactKey,
__QubitRedactValue,
> + ?Sized,
{
#runtime::RedactMapValueMut::redact_map_in_place(value, policy);
}
},
FieldMode::Json => quote_spanned! {field.span()=>
#runtime::__qubit_redact_json! {
#[allow(non_snake_case)]
#[inline(always)]
fn #helper(
value: &mut ::std::string::String,
policy: &#runtime::RedactionPolicy,
) {
#runtime::redact_json_text_in_place(value, policy);
}
}
},
}
}
pub(crate) fn serialization(
type_name: &Ident,
field: &Field,
field_name: &str,
mode: &FieldMode,
serialize_with: Option<&Path>,
context: &crate::serialization_context::SerializationContext<'_>,
) -> TokenStream {
let runtime = context.runtime;
let serde = context.serde;
let generics = context.generics;
let required_trait =
if matches!(mode, FieldMode::Plain) && serialize_with.is_some() {
"SerializeWith"
} else {
mode.serialization_trait_name()
};
let helper = helper_name(type_name, field, field_name, required_trait);
match mode {
FieldMode::Nested => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<'a, __QubitRedactField>(
value: &'a __QubitRedactField,
policy: &'a #runtime::RedactionPolicy,
) -> #runtime::__private::RedactedSerialize<'a, __QubitRedactField>
where
__QubitRedactField:
#runtime::__private::RedactSerialize + ?Sized,
{
#runtime::__private::RedactedSerialize::new(value, policy)
}
},
FieldMode::Map => quote_spanned! {field.span()=>
#[allow(non_snake_case)]
#[inline(always)]
fn #helper<
'a,
__QubitRedactField,
__QubitRedactKey: ?Sized,
__QubitRedactValue: ?Sized,
>(
value: &'a __QubitRedactField,
policy: &'a #runtime::RedactionPolicy,
) -> #runtime::RedactedMap<
'a,
__QubitRedactField,
__QubitRedactKey,
__QubitRedactValue,
>
where
__QubitRedactField:
#runtime::__private::RedactMapSerialize<
__QubitRedactKey,
__QubitRedactValue,
> + ?Sized,
{
#runtime::RedactedMap::new(value, policy.clone())
}
},
FieldMode::Json => quote_spanned! {field.span()=>
#runtime::__qubit_redact_json! {
#[allow(non_snake_case)]
#[allow(clippy::ptr_arg)]
#[inline(always)]
fn #helper<'a>(
value: &'a ::std::string::String,
policy: &'a #runtime::RedactionPolicy,
) -> #runtime::RedactedJsonText<'a, 'a> {
#runtime::RedactedJsonText::new(value, policy)
}
}
},
FieldMode::Plain => serialize_with.map_or_else(TokenStream::new, |path| {
let wrapper = format_ident!("{}_carrier", helper, span = field.span(),);
let field_type = &field.ty;
let carrier_lifetime = generic_bounds::fresh_lifetime(generics);
let mut carrier_generics = generic_bounds::generics_for_field(generics, field_type);
carrier_generics.params.insert(
0,
GenericParam::Lifetime(LifetimeParam::new(carrier_lifetime.clone())),
);
let serializer =
generic_bounds::fresh_identifier(&carrier_generics, "__QubitRedactSerializer");
let carrier_params = &carrier_generics.params;
let (impl_generics, type_generics, where_clause) = carrier_generics.split_for_impl();
quote_spanned! {field.span()=>
#[allow(non_camel_case_types)]
struct #wrapper<#carrier_params>(
&#carrier_lifetime #field_type,
) #where_clause;
impl #impl_generics #serde::Serialize for #wrapper #type_generics #where_clause
{
fn serialize<#serializer>(
&self,
serializer: #serializer,
) -> ::core::result::Result<
#serializer::Ok,
#serializer::Error,
>
where
#serializer: #serde::Serializer,
{
#path(self.0, serializer)
}
}
#[allow(non_snake_case)]
#[inline(always)]
fn #helper #impl_generics(
value: &#carrier_lifetime #field_type,
) -> #wrapper #type_generics {
#wrapper(value)
}
}
}),
FieldMode::Level(_) | FieldMode::Skip => TokenStream::new(),
}
}
pub(crate) fn helper_name(
type_name: &Ident,
field: &Field,
field_name: &str,
required_trait: &str,
) -> Ident {
let type_fragment = type_name.to_string().replace("r#", "");
let field_fragment = field_name.replace("r#", "");
format_ident!(
"__qubit_redact_{}_{}_requires_{}",
type_fragment,
field_fragment,
required_trait,
span = field.span(),
)
}