use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{ItemStruct, parse_macro_input};
pub fn aspect_service(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut entity: Option<String> = None;
let mut model_ident: Option<syn::Ident> = None;
let parse_result = syn::parse::Parser::parse(
|input: syn::parse::ParseStream| {
while !input.is_empty() {
let key: syn::Ident = input.parse()?;
let _: syn::Token![=] = input.parse()?;
if key == "entity" {
let val: syn::LitStr = input.parse()?;
entity = Some(val.value());
} else if key == "model" {
let val: syn::Ident = input.parse()?;
model_ident = Some(val);
} else {
return Err(syn::Error::new(key.span(), "expected `entity` or `model`"));
}
if !input.is_empty() {
let _: syn::Token![,] = input.parse()?;
}
}
Ok(())
},
attr,
);
if let Err(e) = parse_result {
return e.to_compile_error().into();
}
let entity_str = match entity {
Some(e) => e,
None => {
return syn::Error::new(Span::call_site(), "missing `entity = \"...\"` attribute")
.to_compile_error()
.into();
}
};
let model = match model_ident {
Some(m) => m,
None => {
return syn::Error::new(Span::call_site(), "missing `model = Ident` attribute")
.to_compile_error()
.into();
}
};
let input = parse_macro_input!(item as ItemStruct);
let struct_name = &input.ident;
let mut engine_field: Option<syn::Ident> = None;
let mut field_names: Vec<syn::Ident> = Vec::new();
let mut field_types: Vec<syn::Type> = Vec::new();
for field in input.fields.iter() {
let fname = field.ident.clone().unwrap();
let is_engine = field.attrs.iter().any(|a| a.path().is_ident("engine"));
if is_engine {
engine_field = Some(fname.clone());
}
field_names.push(fname);
field_types.push(field.ty.clone());
}
let engine = match engine_field {
Some(e) => e,
None => {
return syn::Error::new(Span::call_site(), "no field marked with `#[engine]` found")
.to_compile_error()
.into();
}
};
let mut clean_input = input.clone();
for field in clean_input.fields.iter_mut() {
field.attrs.retain(|a| !a.path().is_ident("engine"));
}
let model_str = model.to_string();
let event_created = syn::Ident::new(&format!("{}Created", model_str), model.span());
let event_updated = syn::Ident::new(&format!("{}Updated", model_str), model.span());
let event_deleted = syn::Ident::new(&format!("{}Deleted", model_str), model.span());
let expanded = quote! {
#clean_input
impl #struct_name {
pub fn new(#(#field_names: #field_types),*) -> Self {
Self { #(#field_names),* }
}
async fn before_create<T: Clone + serde::Serialize + serde::de::DeserializeOwned + Send>(
&self,
auth: &crate::middleware::auth::AuthUser,
req: T,
) -> crate::errors::app_error::AppResult<(T, crate::aspects::Dispatched)> {
self.#engine.before_create(#entity_str, auth, req).await
}
async fn before_update<T: Clone + serde::Serialize + serde::de::DeserializeOwned + Send>(
&self,
auth: &crate::middleware::auth::AuthUser,
existing: &#model,
req: T,
) -> crate::errors::app_error::AppResult<(T, crate::aspects::Dispatched)> {
self.#engine.before_update(#entity_str, auth, existing, req).await
}
async fn before_delete(
&self,
auth: &crate::middleware::auth::AuthUser,
existing: &#model,
) -> crate::errors::app_error::AppResult<crate::aspects::Dispatched> {
self.#engine.before_delete(#entity_str, auth, existing).await
}
fn after_created(&self, entity: &#model) {
self.#engine.emit(crate::event::Event::#event_created(entity.clone()));
}
fn after_updated(&self, entity: &#model) {
self.#engine.emit(crate::event::Event::#event_updated(entity.clone()));
}
fn after_deleted(&self, entity: &#model) {
self.#engine.emit(crate::event::Event::#event_deleted(entity.clone()));
}
}
};
TokenStream::from(expanded)
}