use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Result, parse_quote};
use crate::path::core_path;
pub fn expand(input: DeriveInput) -> Result<TokenStream> {
let mut trait_only = false;
let mut validator: Option<syn::Path> = None;
let mut error: Option<syn::Type> = None;
let mut serde = false;
for attr in &input.attrs {
if !attr.path().is_ident("entity") {
continue;
}
let mut option_found = false;
attr.parse_nested_meta(|meta| {
option_found = true;
if meta.path.is_ident("validate") {
if validator.is_some() {
return Err(meta.error("duplicate validate attribute"));
}
validator = Some(meta.value()?.parse()?);
return Ok(());
}
if meta.path.is_ident("error") {
if error.is_some() {
return Err(meta.error("duplicate error attribute"));
}
error = Some(meta.value()?.parse()?);
return Ok(());
}
if meta.path.is_ident("serde") {
if !meta.input.is_empty() && !meta.input.peek(syn::Token![,]) {
return Err(meta.error("serde is a flag and takes no value"));
}
if serde {
return Err(meta.error("duplicate serde attribute"));
}
serde = true;
return Ok(());
}
if !meta.path.is_ident("trait_only") {
return Err(meta.error("entity(id) must be placed on a named field"));
}
if !meta.input.is_empty() && !meta.input.peek(syn::Token![,]) {
return Err(meta.error("trait_only is a flag and takes no value"));
}
if trait_only {
return Err(meta.error("duplicate trait_only attribute"));
}
trait_only = true;
Ok(())
})?;
if !option_found {
return Err(syn::Error::new_spanned(
attr,
"entity requires at least one option",
));
}
}
if trait_only && (validator.is_some() || error.is_some() || serde) {
return Err(syn::Error::new_spanned(
&input.ident,
"trait_only cannot be combined with validate, error, or serde",
));
}
if validator.is_some() != error.is_some() {
return Err(syn::Error::new_spanned(
&input.ident,
"validate and error must be specified together",
));
}
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => &fields.named,
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"Entity requires a named-field struct",
));
}
},
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"Entity can only be derived for a named-field struct",
));
}
};
let mut id_field = None;
for field in fields {
if has_id_attribute(field)? {
if id_field.is_some() {
return Err(syn::Error::new_spanned(
field,
"Entity requires exactly one #[entity(id)] field",
));
}
id_field = Some(field);
}
}
let id_field = id_field.ok_or_else(|| {
syn::Error::new_spanned(
&input.ident,
"Entity requires exactly one #[entity(id)] field",
)
})?;
let id_name = id_field.ident.as_ref().expect("named fields have names");
let id_ty = &id_field.ty;
let core = core_path()?;
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let trait_impl = quote! {
impl #impl_generics #core::Entity for #name #ty_generics #where_clause {
type Id = #id_ty;
fn id(&self) -> &Self::Id {
&self.#id_name
}
}
};
if trait_only {
return Ok(trait_impl);
}
if validator.is_some()
&& let Some(field) = fields
.iter()
.find(|field| matches!(field.vis, syn::Visibility::Public(_)))
{
return Err(syn::Error::new_spanned(
field,
"validated Entity fields must not be public",
));
}
let arguments: Vec<_> = fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.collect();
let types: Vec<_> = fields.iter().map(|field| &field.ty).collect();
let constructor = match (&validator, &error) {
(Some(validator), Some(error)) => Some(quote! {
pub fn try_new(#(#arguments: #types),*) -> ::core::result::Result<Self, #error> {
let candidate = Self { #(#arguments),* };
#validator(&candidate)?;
Ok(candidate)
}
}),
_ => None,
};
let serde_impl = if serde {
Some(crate::serde::expand(
&input,
validator.as_ref(),
&core,
true,
)?)
} else {
None
};
let mut partial_eq_generics = input.generics.clone();
partial_eq_generics
.make_where_clause()
.predicates
.push(parse_quote!(#id_ty: ::core::cmp::PartialEq));
let (partial_eq_impl, partial_eq_ty, partial_eq_where) = partial_eq_generics.split_for_impl();
let mut eq_generics = input.generics.clone();
eq_generics
.make_where_clause()
.predicates
.push(parse_quote!(#id_ty: ::core::cmp::Eq));
let (eq_impl, eq_ty, eq_where) = eq_generics.split_for_impl();
let mut hash_generics = input.generics.clone();
hash_generics
.make_where_clause()
.predicates
.push(parse_quote!(#id_ty: ::core::hash::Hash));
let (hash_impl, hash_ty, hash_where) = hash_generics.split_for_impl();
Ok(quote! {
#trait_impl
#serde_impl
impl #impl_generics #name #ty_generics #where_clause {
pub fn id(&self) -> &#id_ty {
&self.#id_name
}
#constructor
}
impl #partial_eq_impl ::core::cmp::PartialEq for #name #partial_eq_ty #partial_eq_where {
fn eq(&self, other: &Self) -> bool {
self.#id_name == other.#id_name
}
}
impl #eq_impl ::core::cmp::Eq for #name #eq_ty #eq_where {}
impl #hash_impl ::core::hash::Hash for #name #hash_ty #hash_where {
fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
::core::hash::Hash::hash(&self.#id_name, state);
}
}
})
}
fn has_id_attribute(field: &syn::Field) -> Result<bool> {
let mut found = false;
for attr in &field.attrs {
if !attr.path().is_ident("entity") {
continue;
}
let mut option_found = false;
attr.parse_nested_meta(|meta| {
option_found = true;
if !meta.path.is_ident("id") {
return Err(meta.error("unknown entity attribute"));
}
if !meta.input.is_empty() {
return Err(meta.error("id is a flag and takes no value"));
}
if found {
return Err(meta.error("duplicate id attribute"));
}
found = true;
Ok(())
})?;
if !option_found {
return Err(syn::Error::new_spanned(attr, "entity requires the id flag"));
}
}
Ok(found)
}