use crate::utils::{apply_derives, ensure_required_fields};
use proc_macro::TokenStream;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
Item, ItemStruct, Result, Token, Type, parse::Parse, parse::ParseStream, parse_macro_input,
};
pub(crate) fn expand(attr: TokenStream, item: TokenStream) -> TokenStream {
let cfg = parse_macro_input!(attr as EntityAttrConfig);
let input = parse_macro_input!(item as Item);
let mut st = match input {
Item::Struct(s) => s,
other => {
return syn::Error::new(other.span(), "#[entity] only on struct")
.to_compile_error()
.into();
}
};
let fields_named = match &mut st.fields {
syn::Fields::Named(f) => f,
_ => {
return syn::Error::new(st.span(), "only supports named-field struct")
.to_compile_error()
.into();
}
};
let id_type = cfg.id_ty.unwrap_or_else(|| syn::parse_quote! { String });
let version_ty: Type = syn::parse_quote! { ::eventide_domain::value_object::Version };
ensure_required_fields(
fields_named,
&[("id", &id_type), ("version", &version_ty)],
true,
);
let mut required: Vec<syn::Path> = vec![
syn::parse_quote!(Default),
syn::parse_quote!(serde::Serialize),
syn::parse_quote!(serde::Deserialize),
];
if cfg.derive_debug.unwrap_or(true) {
required.insert(0, syn::parse_quote!(Debug));
}
apply_derives(&mut st.attrs, required);
let out_struct = ItemStruct { ..st };
let ident = &out_struct.ident;
let generics = out_struct.generics.clone();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let expanded = quote! {
#out_struct
impl #impl_generics ::eventide_domain::entity::Entity for #ident #ty_generics #where_clause {
type Id = #id_type;
fn new(aggregate_id: Self::Id, version: ::eventide_domain::value_object::Version) -> Self {
Self { id: aggregate_id, version, ..Default::default() }
}
fn id(&self) -> &Self::Id { &self.id }
fn version(&self) -> ::eventide_domain::value_object::Version { self.version }
}
};
TokenStream::from(expanded)
}
struct EntityAttrConfig {
id_ty: Option<Type>,
derive_debug: Option<bool>,
}
impl Parse for EntityAttrConfig {
fn parse(input: ParseStream) -> Result<Self> {
let mut id_ty: Option<Type> = None;
let mut derive_debug: Option<bool> = None;
if input.is_empty() {
return Ok(Self {
id_ty,
derive_debug,
});
}
let elems: Punctuated<EntityAttrElem, Token![,]> =
Punctuated::<EntityAttrElem, Token![,]>::parse_terminated(input)?;
for elem in elems.into_iter() {
match elem {
EntityAttrElem::Id(ty) => {
if id_ty.is_some() {
return Err(syn::Error::new(
ty.span(),
"duplicate key 'id' in attribute",
));
}
id_ty = Some(*ty);
}
EntityAttrElem::Debug(b) => {
if derive_debug.is_some() {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
"duplicate key 'debug' in attribute",
));
}
derive_debug = Some(b);
}
}
}
Ok(Self {
id_ty,
derive_debug,
})
}
}
enum EntityAttrElem {
Id(Box<Type>),
Debug(bool),
}
impl Parse for EntityAttrElem {
fn parse(input: ParseStream) -> Result<Self> {
let key: syn::Ident = input.parse()?;
if key == "id" {
let _eq: Token![=] = input.parse()?;
let ty: Type = input.parse()?;
Ok(EntityAttrElem::Id(Box::new(ty)))
} else if key == "debug" {
let _eq: Token![=] = input.parse()?;
let expr: syn::Expr = input.parse()?;
match expr {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(b),
..
}) => Ok(EntityAttrElem::Debug(b.value())),
other => Err(syn::Error::new(
other.span(),
"expected boolean literal for 'debug'",
)),
}
} else {
Err(syn::Error::new(
key.span(),
"unknown key in attribute; expected 'id' or 'debug'",
))
}
}
}