use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{
Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, LitStr, Member, Meta,
Result, Token, Type, ext::IdentExt, parse_quote, punctuated::Punctuated,
};
fn attributes(attrs: &[Attribute], allowed: &[&str]) -> Result<Vec<Attribute>> {
let mut result = Vec::new();
for attr in attrs.iter().filter(|attr| attr.path().is_ident("serde")) {
let options = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
for option in options {
if !allowed.iter().any(|name| option.path().is_ident(name)) {
return Err(syn::Error::new_spanned(
option,
"unsupported serde attribute for generated domain serde; see docs/macro-serde.md",
));
}
}
result.push(attr.clone());
}
Ok(result)
}
const CONTAINER: &[&str] = &[
"rename",
"rename_all",
"rename_all_fields",
"deny_unknown_fields",
"tag",
"content",
"untagged",
"transparent",
"bound",
];
const VARIANT: &[&str] = &[
"rename",
"rename_all",
"alias",
"skip",
"skip_serializing",
"skip_deserializing",
"other",
"untagged",
"bound",
];
const FIELD: &[&str] = &[
"rename",
"alias",
"default",
"flatten",
"skip",
"skip_serializing",
"skip_deserializing",
"skip_serializing_if",
"serialize_with",
"deserialize_with",
"with",
"borrow",
"bound",
];
pub(crate) fn check(input: &DeriveInput) -> Result<()> {
attributes(&input.attrs, CONTAINER)?;
match &input.data {
Data::Struct(data) => {
for field in &data.fields {
attributes(&field.attrs, FIELD)?;
}
}
Data::Enum(data) => {
for variant in &data.variants {
attributes(&variant.attrs, VARIANT)?;
for field in &variant.fields {
attributes(&field.attrs, FIELD)?;
}
}
}
Data::Union(_) => {}
}
Ok(())
}
fn fresh_lifetime(generics: &Generics, stem: &str) -> Lifetime {
let mut name = stem.to_owned();
while generics
.lifetimes()
.any(|param| param.lifetime.ident == name)
{
name.push('x');
}
Lifetime::new(&format!("'{name}"), Span::call_site())
}
fn fields(fields: &Fields, borrow: Option<&Lifetime>) -> Result<TokenStream> {
let definitions = fields
.iter()
.map(|field| {
let attrs = attributes(&field.attrs, FIELD)?;
let ty = &field.ty;
let ty = borrow.map_or_else(|| quote!(#ty), |lt| quote!(&#lt #ty));
let ident = &field.ident;
Ok(if ident.is_some() {
quote!(#(#attrs)* pub #ident: #ty)
} else {
quote!(#(#attrs)* pub #ty)
})
})
.collect::<Result<Vec<_>>>()?;
Ok(match fields {
Fields::Named(_) => quote!({ #(#definitions),* }),
Fields::Unnamed(_) => quote!(( #(#definitions),* )),
Fields::Unit => quote!(),
})
}
fn members(fields: &Fields) -> Vec<Member> {
fields
.iter()
.enumerate()
.map(|(i, field)| {
field
.ident
.clone()
.map(Member::Named)
.unwrap_or_else(|| Member::Unnamed(i.into()))
})
.collect()
}
fn construct(path: TokenStream, fields: &Fields, values: &[TokenStream]) -> TokenStream {
let members = members(fields);
match fields {
Fields::Named(_) => quote!(#path { #(#members: #values),* }),
Fields::Unnamed(_) => quote!(#path(#(#values),*)),
Fields::Unit => path,
}
}
pub(crate) fn expand(
input: &DeriveInput,
validator: Option<&syn::Path>,
core: &TokenStream,
entity: bool,
) -> Result<TokenStream> {
check(input)?;
let name = &input.ident;
let serde = quote!(#core::__private::serde);
let serde_path = LitStr::new(
&format!("{}::__private::serde", core.to_string().replace(' ', "")),
Span::call_site(),
);
let module = format_ident!("__ddd_serde_{}", name);
let ser_name = format_ident!("__DddSerialize{}", name);
let de_name = format_ident!("__DddDeserialize{}", name);
let original = &input.generics;
let fresh_type = |stem: &str| {
let mut name = stem.to_owned();
while original.type_params().any(|p| p.ident == name)
|| original.const_params().any(|p| p.ident == name)
{
name.push('x');
}
format_ident!("{}", name)
};
let serializer = fresh_type("__DddSerializer");
let deserializer = fresh_type("__DddDeserializer");
let (_, original_ty, _) = original.split_for_impl();
let lt = fresh_lifetime(original, "__ddd_ser");
let has_fields = match &input.data {
Data::Struct(data) => !data.fields.is_empty(),
Data::Enum(data) => data.variants.iter().any(|v| !v.fields.is_empty()),
_ => false,
};
let mut ser_generics = original.clone();
if has_fields {
ser_generics
.params
.insert(0, GenericParam::Lifetime(parse_quote!(#lt)));
}
let (ser_params, ser_ty, ser_where) = ser_generics.split_for_impl();
let (de_params, _, de_helper_where) = original.split_for_impl();
let attrs = attributes(&input.attrs, CONTAINER)?;
let default_name = LitStr::new(&name.unraw().to_string(), name.span());
let (mut renamed_ser, mut renamed_de) = (false, false);
for attr in &attrs {
for option in attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)? {
if option.path().is_ident("rename") {
if let Meta::List(list) = option {
for direction in
list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
{
renamed_ser |= direction.path().is_ident("serialize");
renamed_de |= direction.path().is_ident("deserialize");
}
} else {
renamed_ser = true;
renamed_de = true;
}
}
}
}
let ser_rename = (!renamed_ser).then(|| quote!(#[serde(rename(serialize = #default_name))]));
let de_rename = (!renamed_de).then(|| quote!(#[serde(rename(deserialize = #default_name))]));
let mut serialize_arms = Vec::new();
let mut deserialize_arms = Vec::new();
let (ser_definition, de_definition, serialize_body, deserialize_body) = match &input.data {
Data::Struct(data) => {
let transparent = (!entity
&& data.fields.len() == 1
&& !input.attrs.iter().any(|a| {
a.path().is_ident("serde")
&& a.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.is_ok_and(|options| {
options.iter().any(|m| m.path().is_ident("transparent"))
})
}))
.then(|| quote!(#[serde(transparent)]));
let sf = fields(&data.fields, Some(<))?;
let df = fields(&data.fields, None)?;
let (mut ser_def, de_def) = match &data.fields {
Fields::Named(_) => (
quote!(pub struct #ser_name #ser_params #ser_where #sf),
quote!(pub struct #de_name #de_params #de_helper_where #df),
),
_ => (
quote!(pub struct #ser_name #ser_params #sf #ser_where;),
quote!(pub struct #de_name #de_params #df #de_helper_where;),
),
};
let tuple = matches!(data.fields, Fields::Unnamed(_)) && data.fields.len() > 1;
if tuple {
let mut sf = data.fields.clone();
for field in &mut sf {
field.vis = syn::Visibility::Inherited;
field.attrs = attributes(&field.attrs, FIELD)?;
let ty = &field.ty;
field.ty = parse_quote!(&#lt #ty);
}
ser_def = quote! {
#[serde(untagged)]
pub enum #ser_name #ser_params #ser_where { Fields #sf }
};
}
let serialize_path = if tuple {
quote!(#module::#ser_name::Fields)
} else {
quote!(#module::#ser_name)
};
let ms = members(&data.fields);
let borrowed = ms.iter().map(|m| quote!(&self.#m)).collect::<Vec<_>>();
let owned = ms.iter().map(|m| quote!(value.#m)).collect::<Vec<_>>();
(
quote!(#transparent #ser_def),
quote!(#transparent #de_def),
construct(serialize_path, &data.fields, &borrowed),
construct(quote!(Self), &data.fields, &owned),
)
}
Data::Enum(data) => {
let mut ser_variants = Vec::new();
let mut de_variants = Vec::new();
for variant in &data.variants {
let v = &variant.ident;
let va = attributes(&variant.attrs, VARIANT)?;
let mut sf = variant.fields.clone();
let mut df = variant.fields.clone();
for field in &mut sf {
field.attrs = attributes(&field.attrs, FIELD)?;
let ty = &field.ty;
field.ty = parse_quote!(&#lt #ty);
}
for field in &mut df {
field.attrs = attributes(&field.attrs, FIELD)?;
}
ser_variants.push(quote!(#(#va)* #v #sf));
de_variants.push(quote!(#(#va)* #v #df));
let bindings = (0..variant.fields.len())
.map(|i| {
let ident = format_ident!("__ddd_field_{i}");
quote!(#ident)
})
.collect::<Vec<_>>();
let sp = construct(quote!(Self::#v), &variant.fields, &bindings);
let sv = construct(quote!(#module::#ser_name::#v), &variant.fields, &bindings);
let dp = construct(quote!(#module::#de_name::#v), &variant.fields, &bindings);
let dv = construct(quote!(Self::#v), &variant.fields, &bindings);
serialize_arms.push(quote!(#sp => #sv));
deserialize_arms.push(quote!(#dp => #dv));
}
(
quote!(pub enum #ser_name #ser_params #ser_where { #(#ser_variants),* }),
quote!(pub enum #de_name #de_params #de_helper_where { #(#de_variants),* }),
quote!(match self { #(#serialize_arms),* }),
quote!(match value { #(#deserialize_arms),* }),
)
}
Data::Union(_) => unreachable!(),
};
let mut ser_impl_generics = original.clone();
let helper_ser_type: Type = syn::parse2(quote!(#module::#ser_name #ser_ty))?;
ser_impl_generics
.make_where_clause()
.predicates
.push(if has_fields {
parse_quote!(for<#lt> #helper_ser_type: #serde::Serialize)
} else {
parse_quote!(#helper_ser_type: #serde::Serialize)
});
let (ser_impl, _, ser_impl_where) = ser_impl_generics.split_for_impl();
let de_lt = fresh_lifetime(original, "__ddd_de");
let mut de_generics = original.clone();
de_generics
.params
.insert(0, GenericParam::Lifetime(parse_quote!(#de_lt)));
de_generics
.make_where_clause()
.predicates
.push(parse_quote!(#module::#de_name #original_ty: #serde::Deserialize<#de_lt>));
let (de_impl, _, de_where) = de_generics.split_for_impl();
let error = if entity {
"entity validation failed"
} else {
"value object validation failed"
};
let validation_target = match &input.data {
Data::Struct(data) if !entity && data.fields.len() == 1 => {
let member = &members(&data.fields)[0];
quote!(&candidate.#member)
}
_ => quote!(&candidate),
};
let validation = validator.map(|validator| quote! {
#validator(#validation_target).map_err(|_| <#deserializer::Error as #serde::de::Error>::custom(#error))?;
});
Ok(quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
mod #module {
#[allow(unused_imports)]
use super::*;
#[derive(#serde::Serialize)]
#[serde(crate = #serde_path)]
#ser_rename
#de_rename
#(#attrs)*
#ser_definition
#[derive(#serde::Deserialize)]
#[serde(crate = #serde_path)]
#ser_rename
#de_rename
#(#attrs)*
#de_definition
}
impl #ser_impl #serde::Serialize for #name #original_ty #ser_impl_where {
fn serialize<#serializer>(&self, serializer: #serializer) -> ::core::result::Result<#serializer::Ok, #serializer::Error>
where #serializer: #serde::Serializer {
#serde::Serialize::serialize(&(#serialize_body), serializer)
}
}
impl #de_impl #serde::Deserialize<#de_lt> for #name #original_ty #de_where {
fn deserialize<#deserializer>(deserializer: #deserializer) -> ::core::result::Result<Self, #deserializer::Error>
where #deserializer: #serde::Deserializer<#de_lt> {
let value = <#module::#de_name #original_ty as #serde::Deserialize<#de_lt>>::deserialize(deserializer)?;
let candidate = #deserialize_body;
#validation
Ok(candidate)
}
}
})
}