use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, ItemImpl, LitStr, Type};
pub fn expand_entity_config(args: TokenStream, input: TokenStream) -> TokenStream {
let args_span = proc_macro2::Span::call_site();
let parsed: EntityConfigArgs = match syn::parse::<EntityConfigArgs>(args) {
Ok(p) => p,
Err(_) => {
return syn::Error::new(
args_span,
"expected `#[entity(EntityType)]` or `#[entity(EntityType, \"context_key\")]`",
)
.to_compile_error()
.into();
}
};
let entity_ty = parsed.entity_ty;
let context_key_tokens = match parsed.context_key {
Some(key) => quote! { ::core::option::Option::Some(#key) },
None => quote! { ::core::option::Option::None },
};
let item_impl: ItemImpl = parse_macro_input!(input as ItemImpl);
match rewrite_impl(&item_impl, &entity_ty, &context_key_tokens) {
Ok(tokens) => TokenStream::from(tokens),
Err(err) => err.to_compile_error().into(),
}
}
struct EntityConfigArgs {
entity_ty: Type,
context_key: Option<LitStr>,
}
impl syn::parse::Parse for EntityConfigArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let entity_ty: Type = input.parse()?;
let context_key = if input.peek(syn::Token![,]) {
let _: syn::Token![,] = input.parse()?;
Some(input.parse::<LitStr>()?)
} else {
None
};
Ok(EntityConfigArgs {
entity_ty,
context_key,
})
}
}
fn rewrite_impl(
item: &ItemImpl,
entity_ty: &Type,
context_key_tokens: &TokenStream2,
) -> syn::Result<TokenStream2> {
let self_ty = &item.self_ty;
let impl_tokens = item.to_token_stream();
Ok(quote! {
#impl_tokens
rust_ef::inventory::submit!({
rust_ef::registration::EntityConfigRegistration {
type_id: std::any::TypeId::of::<#entity_ty>(),
type_name: stringify!(#entity_ty),
apply_fn: |builder: &mut rust_ef::model_builder::ModelBuilder| {
let meta = <#entity_ty as rust_ef::entity::IEntityType>::entity_meta();
builder.register_entity_meta(meta);
let config = <#self_ty as std::default::Default>::default();
let type_id = std::any::TypeId::of::<#entity_ty>();
let mut entity_builder =
<rust_ef::model_builder::EntityTypeBuilder<'_, #entity_ty>>
::new(builder, type_id);
<#self_ty as rust_ef::model_builder::IEntityTypeConfiguration<#entity_ty>>
::configure(&config, &mut entity_builder);
},
context_key: #context_key_tokens,
}
});
})
}