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                    if let Some(data) = context.event.to_details()? {
164                        return Self::#fn_ident(context, data).await;
165                    }
166
167                    Ok(())
168                })
169            }
170
171            fn aggregator_type(&self) -> &'static str{ #aggregator_ident::name() }
172            fn event_name(&self) -> &'static str { #event_ident::name() }
173        }
174
175        impl #struct_ident {
176            #item
177        }
178    }.into()
179}
180
181/// Implements the [`evento::Aggregator`] trait for structs with event handler methods
182///
183/// The `#[evento::aggregator]` attribute macro automatically implements the [`evento::Aggregator`]
184/// trait by generating an `aggregate` method that dispatches events to the appropriate handler
185/// methods based on event type.
186///
187/// # Syntax
188///
189/// ```ignore
190/// #[evento::aggregator]
191/// impl AggregateStruct {
192///     async fn event_handler_name(&mut self, event: EventDetails<EventType>) -> anyhow::Result<()> {
193///         // Update self based on event
194///         Ok(())
195///     }
196/// }
197/// ```
198///
199/// # Requirements
200///
201/// - The struct must implement all required traits for [`evento::Aggregator`]:
202///   - `Default`, `Send`, `Sync`, `Clone`, `Debug`
203///   - `bincode::Encode`, `bincode::Decode`
204///   - [`evento::AggregatorName`]
205/// - Handler methods must be `async`
206/// - Handler methods must take `&mut self` and `EventDetails<SomeEventType>`
207/// - Handler methods must return `anyhow::Result<()>`
208///
209/// # Event Matching
210///
211/// The macro matches events to handler methods by calling [`evento::Event::to_details`] with
212/// the event type inferred from the handler method's parameter type.
213///
214/// # Examples
215///
216/// ```no_run
217/// use evento::{EventDetails, AggregatorName};
218/// use serde::{Deserialize, Serialize};
219/// use bincode::{Encode, Decode};
220///
221/// #[derive(AggregatorName, Encode, Decode)]
222/// struct UserCreated {
223///     name: String,
224///     email: String,
225/// }
226///
227/// #[derive(AggregatorName, Encode, Decode)]
228/// struct UserEmailChanged {
229///     email: String,
230/// }
231///
232/// #[derive(Default, Serialize, Deserialize, Encode, Decode, Clone, Debug)]
233/// struct User {
234///     name: String,
235///     email: String,
236///     version: u32,
237/// }
238///
239/// #[evento::aggregator]
240/// impl User {
241///     async fn user_created(&mut self, event: EventDetails<UserCreated>) -> anyhow::Result<()> {
242///         self.name = event.data.name;
243///         self.email = event.data.email;
244///         self.version = event.version as u32;
245///         Ok(())
246///     }
247///
248///     async fn user_email_changed(&mut self, event: EventDetails<UserEmailChanged>) -> anyhow::Result<()> {
249///         self.email = event.data.email;
250///         self.version = event.version as u32;
251///         Ok(())
252///     }
253/// }
254/// ```
255///
256/// # Generated Code
257///
258/// The macro generates:
259/// - Implementation of [`evento::Aggregator::aggregate`] with event dispatching
260/// - Implementation of [`evento::Aggregator::revision`] based on a hash of the handler methods
261/// - Implementation of [`evento::AggregatorName`] using the package name and struct name
262#[proc_macro_attribute]
263pub fn aggregator(_attr: TokenStream, item: TokenStream) -> TokenStream {
264    let item: ItemImpl = parse_macro_input!(item);
265    let mut hasher = Sha3_256::new();
266
267    let syn::Type::Path(item_path) = item.self_ty.deref() else {
268        return syn::Error::new_spanned(item, "Unable to find name of impl struct")
269            .into_compile_error()
270            .into();
271    };
272
273    let Some(ident) = item_path.path.get_ident() else {
274        return syn::Error::new_spanned(item, "Unable to get ident of impl struct")
275            .into_compile_error()
276            .into();
277    };
278
279    let handler_fns = item
280        .items
281        .iter()
282        .filter_map(|item| {
283            let syn::ImplItem::Fn(item_fn) = item else {
284                return None;
285            };
286
287            hasher.update(item_fn.to_token_stream().to_string());
288            let ident = item_fn.sig.ident.clone();
289
290            Some(quote! {
291                if let Some(data) = event.to_details()? {
292                    self.#ident(data).await?;
293                    return Ok(());
294                }
295            })
296        })
297        .collect::<proc_macro2::TokenStream>();
298
299    let revision = format!("{:x}", hasher.finalize());
300    let name = ident.to_string();
301
302    quote! {
303        #item
304
305        impl evento::Aggregator for #ident {
306            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>>
307            where
308                Self: Sync + 'async_trait
309            {
310                Box::pin(async move {
311                    #handler_fns
312
313                    Ok(())
314                })
315            }
316
317            fn revision() -> &'static str {
318                #revision
319            }
320        }
321
322        impl evento::AggregatorName for #ident {
323            fn name() -> &'static str {
324                static NAME: std::sync::LazyLock<String> = std::sync::LazyLock::new(||{
325                    format!("{}/{}", env!("CARGO_PKG_NAME"), #name)
326                });
327
328                &NAME
329            }
330        }
331    }
332    .into()
333}
334
335/// Derives the [`evento::AggregatorName`] trait for event types
336///
337/// The `#[derive(AggregatorName)]` macro automatically implements the [`evento::AggregatorName`]
338/// trait, which provides a name identifier for the type. This is essential for event types
339/// as it allows the event system to identify and match events by name.
340///
341/// # Usage
342///
343/// Apply this derive macro to structs that represent events:
344///
345/// ```no_run
346/// use evento::AggregatorName;
347/// use bincode::{Encode, Decode};
348///
349/// #[derive(AggregatorName, Encode, Decode)]
350/// struct UserCreated {
351///     name: String,
352///     email: String,
353/// }
354///
355/// // The derived implementation returns the struct name as a string
356/// assert_eq!(UserCreated::name(), "UserCreated");
357/// ```
358///
359/// # Requirements
360///
361/// Event types using this derive must also implement:
362/// - `bincode::Encode` and `bincode::Decode` for serialization
363/// - Usually derived together: `#[derive(AggregatorName, Encode, Decode)]`
364///
365/// # Generated Implementation
366///
367/// The macro generates a simple implementation that returns the struct name as a static string:
368///
369/// ```ignore
370/// impl evento::AggregatorName for YourEventType {
371///     fn name() -> &'static str {
372///         "YourEventType"
373///     }
374/// }
375/// ```
376///
377/// # Event Identification
378///
379/// The name returned by this trait is used by the event system to:
380/// - Match events to their types during deserialization
381/// - Route events to appropriate handlers
382/// - Filter events in subscriptions
383#[proc_macro_derive(AggregatorName)]
384pub fn derive_aggregator_name(input: TokenStream) -> TokenStream {
385    let ItemStruct { ident, .. } = parse_macro_input!(input);
386    let name = ident.to_string();
387
388    quote! {
389        impl evento::AggregatorName for #ident {
390            fn name() -> &'static str {
391                #name
392            }
393        }
394    }
395    .into()
396}