evento_macro/lib.rs
1//! # Evento Macros
2//!
3//! This crate provides procedural macros for the Evento event sourcing framework.
4//! These macros simplify the implementation of aggregators and event handlers by
5//! generating boilerplate code automatically.
6//!
7//! ## Macros
8//!
9//! - [`aggregator`] - Implements the [`Aggregator`] trait for structs with event handler methods
10//! - [`handler`] - Creates event handler functions for use with subscriptions
11//! - [`AggregatorName`] - Derives the [`AggregatorName`] trait for event types
12//!
13//! ## Usage
14//!
15//! This crate is typically used through the main `evento` crate with the `macro` feature enabled:
16//!
17//! ```toml
18//! [dependencies]
19//! evento = { version = "1.0", features = ["macro"] }
20//! ```
21//!
22//! ## Examples
23//!
24//! See the individual macro documentation for detailed usage examples.
25
26use convert_case::{Case, Casing};
27use proc_macro::TokenStream;
28use quote::{quote, ToTokens};
29use sha3::{Digest, Sha3_256};
30use std::ops::Deref;
31use syn::{parse_macro_input, spanned::Spanned, Ident, ItemFn, ItemImpl, ItemStruct};
32
33/// Creates an event handler for use with event subscriptions
34///
35/// The `#[evento::handler(AggregateType)]` attribute macro transforms a function into an event handler
36/// that can be used with [`evento::subscribe`]. The macro generates the necessary boilerplate to
37/// integrate with the subscription system.
38///
39/// # Syntax
40///
41/// ```ignore
42/// #[evento::handler(AggregateType)]
43/// async fn handler_name<E: evento::Executor>(
44/// context: &evento::Context<'_, E>,
45/// event: EventDetails<EventType>,
46/// ) -> anyhow::Result<()> {
47/// // Handler logic
48/// Ok(())
49/// }
50/// ```
51///
52/// # Parameters
53///
54/// - `AggregateType`: The aggregate type this handler is associated with
55///
56/// # Function Requirements
57///
58/// The decorated function must:
59/// - Be `async`
60/// - Take `&evento::Context<'_, E>` as first parameter where `E: evento::Executor`
61/// - Take `EventDetails<SomeEventType>` as second parameter
62/// - Return `anyhow::Result<()>`
63///
64/// # Examples
65///
66/// ```no_run
67/// use evento::{Context, EventDetails, Executor};
68/// use bincode::{Encode, Decode};
69///
70/// # use evento::AggregatorName;
71/// # #[derive(AggregatorName, Encode, Decode)]
72/// # struct UserCreated { name: String }
73/// # #[derive(Default, Encode, Decode, Clone, Debug)]
74/// # struct User;
75/// # #[evento::aggregator]
76/// # impl User {}
77///
78/// #[evento::handler(User)]
79/// async fn on_user_created<E: Executor>(
80/// context: &Context<'_, E>,
81/// event: EventDetails<UserCreated>,
82/// ) -> anyhow::Result<()> {
83/// println!("User created: {}", event.data.name);
84///
85/// // Can trigger side effects, call external services, etc.
86///
87/// Ok(())
88/// }
89///
90/// // Use with subscription
91/// # async fn setup(executor: evento::Sqlite) -> anyhow::Result<()> {
92/// evento::subscribe("user-handlers")
93/// .aggregator::<User>()
94/// .handler(on_user_created())
95/// .run(&executor)
96/// .await?;
97/// # Ok(())
98/// # }
99/// ```
100///
101/// # Generated Code
102///
103/// The macro generates:
104/// - A struct implementing [`evento::SubscribeHandler`]
105/// - A constructor function returning an instance of that struct
106/// - Type-safe event filtering based on the event type
107#[proc_macro_attribute]
108pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
109 let item: ItemFn = parse_macro_input!(item);
110 let fn_ident = item.sig.ident.to_owned();
111 let struct_ident = item.sig.ident.to_string().to_case(Case::UpperCamel);
112 let struct_ident = Ident::new(&struct_ident, item.span());
113 let aggregator_ident = Ident::new(&attr.to_string(), item.span());
114 let Some(syn::FnArg::Typed(event_arg)) = item.sig.inputs.get(1) else {
115 return syn::Error::new_spanned(item, "Unable to find event input")
116 .into_compile_error()
117 .into();
118 };
119
120 let syn::Type::Path(ty) = *event_arg.ty.clone() else {
121 return syn::Error::new_spanned(item, "Unable to find event input type")
122 .into_compile_error()
123 .into();
124 };
125
126 let Some(event_arg_segment) = ty.path.segments.first() else {
127 return syn::Error::new_spanned(item, "Unable to find event input type iden")
128 .into_compile_error()
129 .into();
130 };
131
132 let syn::PathArguments::AngleBracketed(ref arguments) = event_arg_segment.arguments else {
133 return syn::Error::new_spanned(item, "Unable to find event input type iden")
134 .into_compile_error()
135 .into();
136 };
137
138 let Some(syn::GenericArgument::Type(syn::Type::Path(ty))) = arguments.args.first() else {
139 return syn::Error::new_spanned(item, "Unable to find event input type iden")
140 .into_compile_error()
141 .into();
142 };
143
144 let Some(segment) = ty.path.segments.first() else {
145 return syn::Error::new_spanned(item, "Unable to find event input type iden")
146 .into_compile_error()
147 .into();
148 };
149
150 let event_ident = segment.ident.to_owned();
151
152 quote! {
153 struct #struct_ident;
154
155 fn #fn_ident() -> #struct_ident { #struct_ident }
156
157 impl<E: evento::Executor> evento::SubscribeHandler<E> for #struct_ident {
158 fn handle<'async_trait>(&'async_trait self, context: &'async_trait evento::Context<'_, E>) -> std::pin::Pin<Box<dyn std::future::Future<Output=anyhow::Result<()>> + Send + 'async_trait>>
159 where
160 Self: Sync + 'async_trait
161 {
162 Box::pin(async move {
163 let Ok(details) = context.event.to_details() else {
164 anyhow::bail!("Failed to deserialize event details data/metadata, it seams that evento::save(id).data(a).metadata(b) and handler(event: EventDetails<a,b>) not matching.");
165 };
166
167 if let Some(data) = details {
168 return Self::#fn_ident(context, data).await;
169 }
170
171 Ok(())
172 })
173 }
174
175 fn aggregator_type(&self) -> &'static str{ #aggregator_ident::name() }
176 fn event_name(&self) -> &'static str { #event_ident::name() }
177 }
178
179 impl #struct_ident {
180 #item
181 }
182 }.into()
183}
184
185/// Implements the [`evento::Aggregator`] trait for structs with event handler methods
186///
187/// The `#[evento::aggregator]` attribute macro automatically implements the [`evento::Aggregator`]
188/// trait by generating an `aggregate` method that dispatches events to the appropriate handler
189/// methods based on event type.
190///
191/// # Syntax
192///
193/// ```ignore
194/// #[evento::aggregator]
195/// impl AggregateStruct {
196/// async fn event_handler_name(&mut self, event: EventDetails<EventType>) -> anyhow::Result<()> {
197/// // Update self based on event
198/// Ok(())
199/// }
200/// }
201/// ```
202///
203/// # Requirements
204///
205/// - The struct must implement all required traits for [`evento::Aggregator`]:
206/// - `Default`, `Send`, `Sync`, `Clone`, `Debug`
207/// - `bincode::Encode`, `bincode::Decode`
208/// - [`evento::AggregatorName`]
209/// - Handler methods must be `async`
210/// - Handler methods must take `&mut self` and `EventDetails<SomeEventType>`
211/// - Handler methods must return `anyhow::Result<()>`
212///
213/// # Event Matching
214///
215/// The macro matches events to handler methods by calling [`evento::Event::to_details`] with
216/// the event type inferred from the handler method's parameter type.
217///
218/// # Examples
219///
220/// ```no_run
221/// use evento::{EventDetails, AggregatorName};
222/// use serde::{Deserialize, Serialize};
223/// use bincode::{Encode, Decode};
224///
225/// #[derive(AggregatorName, Encode, Decode)]
226/// struct UserCreated {
227/// name: String,
228/// email: String,
229/// }
230///
231/// #[derive(AggregatorName, Encode, Decode)]
232/// struct UserEmailChanged {
233/// email: String,
234/// }
235///
236/// #[derive(Default, Serialize, Deserialize, Encode, Decode, Clone, Debug)]
237/// struct User {
238/// name: String,
239/// email: String,
240/// version: u32,
241/// }
242///
243/// #[evento::aggregator]
244/// impl User {
245/// async fn user_created(&mut self, event: EventDetails<UserCreated>) -> anyhow::Result<()> {
246/// self.name = event.data.name;
247/// self.email = event.data.email;
248/// self.version = event.version as u32;
249/// Ok(())
250/// }
251///
252/// async fn user_email_changed(&mut self, event: EventDetails<UserEmailChanged>) -> anyhow::Result<()> {
253/// self.email = event.data.email;
254/// self.version = event.version as u32;
255/// Ok(())
256/// }
257/// }
258/// ```
259///
260/// # Generated Code
261///
262/// The macro generates:
263/// - Implementation of [`evento::Aggregator::aggregate`] with event dispatching
264/// - Implementation of [`evento::Aggregator::revision`] based on a hash of the handler methods
265/// - Implementation of [`evento::AggregatorName`] using the package name and struct name
266#[proc_macro_attribute]
267pub fn aggregator(_attr: TokenStream, item: TokenStream) -> TokenStream {
268 let item: ItemImpl = parse_macro_input!(item);
269 let mut hasher = Sha3_256::new();
270
271 let syn::Type::Path(item_path) = item.self_ty.deref() else {
272 return syn::Error::new_spanned(item, "Unable to find name of impl struct")
273 .into_compile_error()
274 .into();
275 };
276
277 let Some(ident) = item_path.path.get_ident() else {
278 return syn::Error::new_spanned(item, "Unable to get ident of impl struct")
279 .into_compile_error()
280 .into();
281 };
282
283 let handler_fns = item
284 .items
285 .iter()
286 .filter_map(|item| {
287 let syn::ImplItem::Fn(item_fn) = item else {
288 return None;
289 };
290
291 hasher.update(item_fn.to_token_stream().to_string());
292 let ident = item_fn.sig.ident.clone();
293
294 Some(quote! {
295 let Ok(details) = event.to_details() else {
296 anyhow::bail!("Failed to deserialize event details data/metadata, it seams that evento::save(id).data(a).metadata(b) and handler(event: EventDetails<a,b>) not matching.");
297 };
298
299 if let Some(data) = details {
300 self.#ident(data).await?;
301 return Ok(());
302 }
303 })
304 })
305 .collect::<proc_macro2::TokenStream>();
306
307 let revision = format!("{:x}", hasher.finalize());
308 let name = ident.to_string();
309
310 quote! {
311 #item
312
313 impl evento::Aggregator for #ident {
314 fn aggregate<'async_trait>(&'async_trait mut self, event: &'async_trait evento::Event) -> std::pin::Pin<Box<dyn std::future::Future<Output=anyhow::Result<()>> + Send + 'async_trait>>
315 where
316 Self: Sync + 'async_trait
317 {
318 Box::pin(async move {
319 #handler_fns
320
321 Ok(())
322 })
323 }
324
325 fn revision() -> &'static str {
326 #revision
327 }
328 }
329
330 impl evento::AggregatorName for #ident {
331 fn name() -> &'static str {
332 static NAME: std::sync::LazyLock<String> = std::sync::LazyLock::new(||{
333 format!("{}/{}", env!("CARGO_PKG_NAME"), #name)
334 });
335
336 &NAME
337 }
338 }
339 }
340 .into()
341}
342
343/// Derives the [`evento::AggregatorName`] trait for event types
344///
345/// The `#[derive(AggregatorName)]` macro automatically implements the [`evento::AggregatorName`]
346/// trait, which provides a name identifier for the type. This is essential for event types
347/// as it allows the event system to identify and match events by name.
348///
349/// # Usage
350///
351/// Apply this derive macro to structs that represent events:
352///
353/// ```no_run
354/// use evento::AggregatorName;
355/// use bincode::{Encode, Decode};
356///
357/// #[derive(AggregatorName, Encode, Decode)]
358/// struct UserCreated {
359/// name: String,
360/// email: String,
361/// }
362///
363/// // The derived implementation returns the struct name as a string
364/// assert_eq!(UserCreated::name(), "UserCreated");
365/// ```
366///
367/// # Requirements
368///
369/// Event types using this derive must also implement:
370/// - `bincode::Encode` and `bincode::Decode` for serialization
371/// - Usually derived together: `#[derive(AggregatorName, Encode, Decode)]`
372///
373/// # Generated Implementation
374///
375/// The macro generates a simple implementation that returns the struct name as a static string:
376///
377/// ```ignore
378/// impl evento::AggregatorName for YourEventType {
379/// fn name() -> &'static str {
380/// "YourEventType"
381/// }
382/// }
383/// ```
384///
385/// # Event Identification
386///
387/// The name returned by this trait is used by the event system to:
388/// - Match events to their types during deserialization
389/// - Route events to appropriate handlers
390/// - Filter events in subscriptions
391#[proc_macro_derive(AggregatorName)]
392pub fn derive_aggregator_name(input: TokenStream) -> TokenStream {
393 let ItemStruct { ident, .. } = parse_macro_input!(input);
394 let name = ident.to_string();
395
396 quote! {
397 impl evento::AggregatorName for #ident {
398 fn name() -> &'static str {
399 #name
400 }
401 }
402 }
403 .into()
404}