use proc_macro2::TokenStream;
use quote::quote;
use syn::{
DeriveInput,
Generics,
Path,
};
use crate::{
field_assertion,
generic_bounds,
internal::{
ContainerData,
FieldsData,
VariantData,
},
serde_container_attributes::SerdeContainerAttributes,
serialization_context::SerializationContext,
};
use super::{
enum_expansion::enum_body,
field_serialization::field_context,
struct_expansion::struct_body,
};
pub(crate) fn expand(
input: &DeriveInput,
runtime: &Path,
serde: Option<&Path>,
container_attributes: &SerdeContainerAttributes,
model: &ContainerData<'_>,
) -> syn::Result<TokenStream> {
let Some(serde) = serde else {
return Ok(TokenStream::new());
};
let serialization_assertions = serialization_assertions(
&input.ident,
model,
runtime,
serde,
&input.generics,
);
let serializer = generic_bounds::fresh_identifier(
&input.generics,
"__QubitRedactSerializer",
);
let body = match model {
ContainerData::Struct(fields) => struct_body(
&input.ident,
fields,
runtime,
serde,
container_attributes,
),
ContainerData::Enum(variants) => enum_body(
&input.ident,
variants,
runtime,
serde,
container_attributes,
&serializer,
)?,
};
let mut serialization_generics = input.generics.clone();
generic_bounds::add_serialization_bounds(
&mut serialization_generics,
model,
runtime,
serde,
);
let name = &input.ident;
let (impl_generics, type_generics, where_clause) =
serialization_generics.split_for_impl();
Ok(quote! {
#runtime::__qubit_redact_serde! {
impl #impl_generics #runtime::__private::RedactSerialize
for #name #type_generics #where_clause
{
fn serialize_redacted<#serializer>(
&self,
policy: &#runtime::RedactionPolicy,
serializer: #serializer,
) -> ::core::result::Result<
#serializer::Ok,
#serializer::Error,
>
where
#serializer: #serde::Serializer,
{
#(#serialization_assertions)*
#body
}
}
impl #impl_generics #serde::Serialize
for #name #type_generics #where_clause
{
fn serialize<#serializer>(
&self,
serializer: #serializer,
) -> ::core::result::Result<
#serializer::Ok,
#serializer::Error,
>
where
#serializer: #serde::Serializer,
{
let policy = #runtime::RedactionPolicy::default();
<Self as #runtime::__private::RedactSerialize>::serialize_redacted(
self,
&policy,
serializer,
)
}
}
}
})
}
fn serialization_assertions(
type_name: &syn::Ident,
model: &ContainerData<'_>,
runtime: &Path,
serde: &Path,
generics: &Generics,
) -> Vec<TokenStream> {
match model {
ContainerData::Struct(fields) => fields_serialization_assertions(
type_name, fields, None, runtime, serde, generics,
),
ContainerData::Enum(variants) => variants
.iter()
.flat_map(|variant| {
fields_serialization_assertions(
type_name,
variant.fields(),
Some(variant),
runtime,
serde,
generics,
)
})
.collect(),
}
}
fn fields_serialization_assertions(
type_name: &syn::Ident,
fields: &FieldsData<'_>,
variant: Option<&VariantData<'_>>,
runtime: &Path,
serde: &Path,
generics: &Generics,
) -> Vec<TokenStream> {
let context = SerializationContext {
runtime,
serde,
generics,
};
match fields {
FieldsData::Named(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.identifier().to_string();
let field_context = field_context(
variant.map(|item| &item.variant().ident),
variant.map(VariantData::index),
&field_name,
);
field_assertion::serialization(
type_name,
parsed.field(),
&field_context,
parsed.attributes().mode(),
parsed.serde_attributes().serialize_with(),
&context,
)
})
.collect(),
FieldsData::Unnamed(fields) => fields
.iter()
.map(|parsed| {
let field_name = parsed.index().index.to_string();
let field_context = field_context(
variant.map(|item| &item.variant().ident),
variant.map(VariantData::index),
&field_name,
);
field_assertion::serialization(
type_name,
parsed.field(),
&field_context,
parsed.attributes().mode(),
parsed.serde_attributes().serialize_with(),
&context,
)
})
.collect(),
FieldsData::Unit => Vec::new(),
}
}