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}