use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use super::{
result_gen::command_result_type,
struct_gen::{command_struct_name, custom_payload_type, uses_custom_payload}
};
use crate::{
entity::parse::{CommandDef, EntityDef},
utils::marker
};
pub fn generate(entity: &EntityDef) -> TokenStream {
let commands = entity.command_defs();
if commands.is_empty() {
return TokenStream::new();
}
let vis = &entity.vis;
let entity_name = entity.name();
let trait_name = format_ident!("{}CommandHandler", entity_name);
let command_enum = format_ident!("{}Command", entity_name);
let result_enum = format_ident!("{}CommandResult", entity_name);
let marker = marker::generated();
let handler_methods = generate_handler_methods(entity, commands);
let dispatch_arms = generate_dispatch_arms(entity, commands);
let doc = format!(
"Command handler trait for [`{}`] entity.\n\n\
Implement this trait to handle business commands.\n\n\
# Default Implementation\n\n\
The `handle` method dispatches to individual handlers.\n\
Override individual `handle_*` methods for your business logic.",
entity_name
);
quote! {
#marker
#[doc = #doc]
#[async_trait::async_trait]
#vis trait #trait_name: Send + Sync {
type Error: std::error::Error + Send + Sync;
type Context: Send + Sync;
async fn handle(
&self,
cmd: #command_enum,
ctx: &Self::Context
) -> Result<#result_enum, Self::Error> {
match cmd {
#dispatch_arms
}
}
#handler_methods
}
}
}
fn generate_handler_methods(entity: &EntityDef, commands: &[CommandDef]) -> TokenStream {
let methods: Vec<TokenStream> = commands
.iter()
.map(|cmd| generate_handler_method(entity, cmd))
.collect();
quote! { #(#methods)* }
}
fn generate_handler_method(entity: &EntityDef, cmd: &CommandDef) -> TokenStream {
let method_name = cmd.handler_method_name();
let payload_type = if uses_custom_payload(cmd) {
let ty = custom_payload_type(cmd).unwrap();
quote! { #ty }
} else {
let struct_name = command_struct_name(entity, cmd);
quote! { #struct_name }
};
let result_type = if let Some(ty) = command_result_type(entity, cmd) {
quote! { #ty }
} else {
quote! { () }
};
let doc = format!(
"Handle {} command.\n\n\
Override this method to implement your business logic.",
cmd.name
);
quote! {
#[doc = #doc]
async fn #method_name(
&self,
cmd: #payload_type,
ctx: &Self::Context
) -> Result<#result_type, Self::Error>;
}
}
fn generate_dispatch_arms(entity: &EntityDef, commands: &[CommandDef]) -> TokenStream {
let entity_name = entity.name();
let command_enum = format_ident!("{}Command", entity_name);
let result_enum = format_ident!("{}CommandResult", entity_name);
let arms: Vec<TokenStream> = commands
.iter()
.map(|cmd| {
let variant_name = &cmd.name;
let method_name = cmd.handler_method_name();
let result_wrap = if command_result_type(entity, cmd).is_some() {
quote! { #result_enum::#variant_name(result) }
} else {
quote! { { let _ = result; #result_enum::#variant_name } }
};
quote! {
#command_enum::#variant_name(payload) => {
let result = self.#method_name(payload, ctx).await?;
Ok(#result_wrap)
}
}
})
.collect();
quote! { #(#arms)* }
}