Skip to main content

evento_macro/
lib.rs

1//! Procedural macros for the Evento event sourcing framework.
2//!
3//! This crate provides macros that eliminate boilerplate when building event-sourced
4//! applications with Evento. It generates trait implementations, handler structs,
5//! and serialization code automatically.
6//!
7//! # Macros
8//!
9//! | Macro | Type | Purpose |
10//! |-------|------|---------|
11//! | [`aggregator`] | Attribute | Transform enum into event structs with trait impls |
12//! | [`handler`] | Attribute | Create projection handler from async function |
13//! | [`subscription`] | Attribute | Create subscription handler for specific events |
14//! | [`subscription_all`] | Attribute | Create subscription handler for all events of an aggregate |
15//! | [`projection`] | Attribute | Add cursor field and implement `ProjectionCursor` |
16//! | [`Cursor`] | Derive | Generate cursor struct and trait implementations |
17//! | [`debug_handler`] | Attribute | Like `handler` but outputs generated code for debugging |
18//!
19//! # Usage
20//!
21//! This crate is typically used through the main `evento` crate with the `macro` feature
22//! enabled (on by default):
23//!
24//! ```toml
25//! [dependencies]
26//! evento = "2"
27//! ```
28//!
29//! # Examples
30//!
31//! ## Defining Events with `#[evento::aggregator]`
32//!
33//! Transform an enum into individual event structs:
34//!
35//! ```rust,ignore
36//! #[evento::aggregator]
37//! pub enum BankAccount {
38//!     /// Event raised when a new bank account is opened
39//!     AccountOpened {
40//!         owner_id: String,
41//!         owner_name: String,
42//!         initial_balance: i64,
43//!     },
44//!
45//!     MoneyDeposited {
46//!         amount: i64,
47//!         transaction_id: String,
48//!     },
49//!
50//!     MoneyWithdrawn {
51//!         amount: i64,
52//!         transaction_id: String,
53//!     },
54//! }
55//! ```
56//!
57//! This generates:
58//! - `AccountOpened`, `MoneyDeposited`, `MoneyWithdrawn` structs
59//! - `Aggregator` and `Event` trait implementations for each
60//! - Automatic derives: `Debug`, `Clone`, `PartialEq`, `Default`, and bitcode serialization
61//!
62//! ## Creating Projection Handlers with `#[evento::handler]`
63//!
64//! Projection handlers are used to build read models by replaying events:
65//!
66//! ```rust,ignore
67//! use evento::metadata::Event;
68//!
69//! #[evento::handler]
70//! async fn handle_money_deposited(
71//!     event: Event<MoneyDeposited>,
72//!     projection: &mut AccountBalanceView,
73//! ) -> anyhow::Result<()> {
74//!     projection.balance += event.data.amount;
75//!     Ok(())
76//! }
77//!
78//! // Use in a projection
79//! let projection = Projection::<_, AccountBalanceView>::new::<BankAccount>()
80//!     .handler(handle_money_deposited())
81//!     .load("account-123");
82//! ```
83//!
84//! ## Creating Subscription Handlers with `#[evento::subscription]`
85//!
86//! Subscription handlers process events in real-time with side effects:
87//!
88//! ```rust,ignore
89//! use evento::{Executor, metadata::Event, subscription::Context};
90//!
91//! #[evento::subscription]
92//! async fn on_money_deposited<E: Executor>(
93//!     context: &Context<'_, E>,
94//!     event: Event<MoneyDeposited>,
95//! ) -> anyhow::Result<()> {
96//!     // Perform side effects: send notifications, update read models, etc.
97//!     println!("Deposited: {}", event.data.amount);
98//!     Ok(())
99//! }
100//!
101//! // Use in a subscription
102//! let subscription = SubscriptionBuilder::<Sqlite>::new("deposit-notifier")
103//!     .handler(on_money_deposited())
104//!     .routing_key("accounts")
105//!     .start(&executor)
106//!     .await?;
107//! ```
108//!
109//! ## Handling All Events with `#[evento::subscription_all]`
110//!
111//! Handle all events from an aggregate type without deserializing:
112//!
113//! ```rust,ignore
114//! use evento::{Executor, metadata::RawEvent, subscription::Context};
115//!
116//! #[evento::subscription_all]
117//! async fn on_any_account_event<E: Executor>(
118//!     context: &Context<'_, E>,
119//!     event: RawEvent<BankAccount>,
120//! ) -> anyhow::Result<()> {
121//!     println!("Event {} on account {}", event.name, event.aggregator_id);
122//!     Ok(())
123//! }
124//! ```
125//!
126//! ## Projection State with `#[evento::projection]`
127//!
128//! Automatically add cursor tracking to projection structs:
129//!
130//! ```rust,ignore
131//! #[evento::projection]
132//! #[derive(Debug)]
133//! pub struct AccountBalanceView {
134//!     pub balance: i64,
135//!     pub owner: String,
136//! }
137//!
138//! // Generates:
139//! // - Adds `pub cursor: String` field
140//! // - Implements `ProjectionCursor` trait
141//! // - Adds `Default` and `Clone` derives
142//! ```
143//!
144//! # Requirements
145//!
146//! When using these macros, your types must meet certain requirements:
147//!
148//! - **Events** (from `#[aggregator]`): Automatically derive required traits
149//! - **Projections**: Must implement `Default`, `Send`, `Sync`, `Clone`
150//! - **Projection handlers**: Must be `async` and return `anyhow::Result<()>`
151//! - **Subscription handlers**: Must be `async`, take `Context` first, and return `anyhow::Result<()>`
152//!
153//! # Serialization
154//!
155//! Events are serialized using [bitcode](https://crates.io/crates/bitcode) for compact
156//! binary representation. The `#[aggregator]` macro automatically adds the required
157//! bitcode derives.
158
159mod aggregator;
160mod cursor;
161mod handler;
162mod projection;
163mod subscription;
164mod subscription_all;
165
166use proc_macro::TokenStream;
167use syn::{parse_macro_input, DeriveInput, ItemFn};
168
169/// Transforms an enum into individual event structs with trait implementations.
170///
171/// This macro takes an enum where each variant represents an event type and generates:
172/// - Individual public structs for each variant
173/// - `Aggregator` trait implementation (provides `aggregator_type()`)
174/// - `AggregatorEvent` trait implementation (provides `event_name()`)
175/// - A unit struct with the enum name implementing `Aggregator`
176/// - Automatic derives: `Debug`, `Clone`, `PartialEq`, `Default`, and bitcode serialization
177///
178/// # Aggregator Type Format
179///
180/// The aggregator type is formatted as `"{package_name}/{enum_name}"`, e.g., `"bank/BankAccount"`.
181///
182/// # Example
183///
184/// ```rust,ignore
185/// #[evento::aggregator]
186/// pub enum BankAccount {
187///     /// Event raised when account is opened
188///     AccountOpened {
189///         owner_id: String,
190///         owner_name: String,
191///         initial_balance: i64,
192///     },
193///
194///     MoneyDeposited {
195///         amount: i64,
196///         transaction_id: String,
197///     },
198/// }
199///
200/// // Generated structs can be used directly:
201/// let event = AccountOpened {
202///     owner_id: "user123".into(),
203///     owner_name: "John".into(),
204///     initial_balance: 1000,
205/// };
206/// ```
207///
208/// # Additional Derives
209///
210/// Pass additional derives as arguments:
211///
212/// ```rust,ignore
213/// #[evento::aggregator(serde::Serialize, serde::Deserialize)]
214/// pub enum MyEvents {
215///     // variants...
216/// }
217/// ```
218///
219/// # Variant Types
220///
221/// Supports all enum variant types:
222/// - Named fields: `Variant { field: Type }`
223/// - Tuple fields: `Variant(Type1, Type2)`
224/// - Unit variants: `Variant`
225#[proc_macro_attribute]
226pub fn aggregator(attr: TokenStream, item: TokenStream) -> TokenStream {
227    aggregator::aggregator(attr, item)
228}
229
230/// Creates a projection handler from an async function.
231///
232/// This macro transforms an async function into a handler struct that implements
233/// the `projection::Handler<P>` trait for use with projections to build read models.
234///
235/// # Function Signature
236///
237/// The function must have this signature:
238///
239/// ```rust,ignore
240/// async fn handler_name(
241///     event: Event<EventType>,
242///     projection: &mut ProjectionType,
243/// ) -> anyhow::Result<()>
244/// ```
245///
246/// # Generated Code
247///
248/// For a function `handle_money_deposited`, the macro generates:
249/// - `HandleMoneyDepositedHandler` struct
250/// - `handle_money_deposited()` constructor function
251/// - `projection::Handler<ProjectionType>` trait implementation
252///
253/// # Example
254///
255/// ```rust,ignore
256/// use evento::metadata::Event;
257///
258/// #[evento::handler]
259/// async fn handle_money_deposited(
260///     event: Event<MoneyDeposited>,
261///     projection: &mut AccountBalanceView,
262/// ) -> anyhow::Result<()> {
263///     projection.balance += event.data.amount;
264///     Ok(())
265/// }
266///
267/// // Register with projection
268/// let projection = Projection::<_, AccountBalanceView>::new::<BankAccount>()
269///     .handler(handle_money_deposited());
270///
271/// // Execute projection to get current state
272/// let result = projection.load("account-123").execute(&executor).await?;
273/// ```
274#[proc_macro_attribute]
275pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
276    let input = parse_macro_input!(item as ItemFn);
277
278    match handler::handler_next_impl(&input, false) {
279        Ok(tokens) => tokens,
280        Err(e) => e.to_compile_error().into(),
281    }
282}
283
284/// Debug variant of [`handler`] that writes generated code to a file.
285///
286/// The generated code is written to `target/evento_debug_handler_macro.rs`
287/// for inspection. Useful for understanding what the macro produces.
288///
289/// # Example
290///
291/// ```rust,ignore
292/// #[evento::debug_handler]
293/// async fn handle_event<E: Executor>(
294///     event: Event<MyEvent>,
295///     action: Action<'_, MyView, E>,
296/// ) -> anyhow::Result<()> {
297///     // ...
298/// }
299/// ```
300#[proc_macro_attribute]
301pub fn debug_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
302    let input = parse_macro_input!(item as ItemFn);
303
304    match handler::handler_next_impl(&input, true) {
305        Ok(tokens) => tokens,
306        Err(e) => e.to_compile_error().into(),
307    }
308}
309
310/// Creates a subscription handler for specific events.
311///
312/// This macro transforms an async function into a handler struct that implements
313/// the `subscription::Handler<E>` trait for processing events in real-time subscriptions.
314///
315/// Unlike projection handlers, subscription handlers receive a context with access
316/// to the executor and can perform side effects like database updates, notifications,
317/// or external API calls.
318///
319/// # Function Signature
320///
321/// The function must have this signature:
322///
323/// ```rust,ignore
324/// async fn handler_name<E: Executor>(
325///     context: &Context<'_, E>,
326///     event: Event<EventType>,
327/// ) -> anyhow::Result<()>
328/// ```
329///
330/// # Generated Code
331///
332/// For a function `on_money_deposited`, the macro generates:
333/// - `OnMoneyDepositedHandler` struct
334/// - `on_money_deposited()` constructor function
335/// - `subscription::Handler<E>` trait implementation
336///
337/// # Example
338///
339/// ```rust,ignore
340/// use evento::{Executor, metadata::Event, subscription::Context};
341///
342/// #[evento::subscription]
343/// async fn on_money_deposited<E: Executor>(
344///     context: &Context<'_, E>,
345///     event: Event<MoneyDeposited>,
346/// ) -> anyhow::Result<()> {
347///     // Access shared data from context
348///     let config: Data<AppConfig> = context.extract();
349///
350///     // Perform side effects
351///     send_notification(&event.data).await?;
352///
353///     Ok(())
354/// }
355///
356/// // Register with subscription
357/// let subscription = SubscriptionBuilder::<Sqlite>::new("deposit-notifier")
358///     .handler(on_money_deposited())
359///     .routing_key("accounts")
360///     .start(&executor)
361///     .await?;
362/// ```
363#[proc_macro_attribute]
364pub fn subscription(_attr: TokenStream, item: TokenStream) -> TokenStream {
365    let input = parse_macro_input!(item as ItemFn);
366
367    match subscription::subscription_next_impl(&input, false) {
368        Ok(tokens) => tokens,
369        Err(e) => e.to_compile_error().into(),
370    }
371}
372
373/// Creates a subscription handler that processes all events of an aggregate type.
374///
375/// This macro is similar to [`subscription`] but handles all events from an aggregate
376/// without requiring the event data to be deserialized. The event is wrapped in
377/// [`RawEvent`](evento_core::metadata::RawEvent) which provides access to event metadata
378/// (name, id, timestamp, etc.) without deserializing the payload.
379///
380/// # Function Signature
381///
382/// The function must have this signature:
383///
384/// ```rust,ignore
385/// async fn handler_name<E: Executor>(
386///     context: &Context<'_, E>,
387///     event: RawEvent<AggregateType>,
388/// ) -> anyhow::Result<()>
389/// ```
390///
391/// # Generated Code
392///
393/// For a function `on_any_account_event`, the macro generates:
394/// - `OnAnyAccountEventHandler` struct
395/// - `on_any_account_event()` constructor function
396/// - `subscription::Handler<E>` trait implementation with `event_name()` returning `"all"`
397///
398/// # Example
399///
400/// ```rust,ignore
401/// use evento::{Executor, metadata::RawEvent, subscription::Context};
402///
403/// #[evento::subscription_all]
404/// async fn on_any_account_event<E: Executor>(
405///     context: &Context<'_, E>,
406///     event: RawEvent<BankAccount>,
407/// ) -> anyhow::Result<()> {
408///     // Access event metadata without deserializing
409///     println!("Event: {} on {}", event.name, event.aggregator_id);
410///     println!("Version: {}", event.version);
411///     println!("Timestamp: {}", event.timestamp);
412///
413///     // Useful for logging, auditing, or forwarding events
414///     Ok(())
415/// }
416///
417/// // Register with subscription - handles all BankAccount events
418/// let subscription = SubscriptionBuilder::<Sqlite>::new("account-auditor")
419///     .handler(on_any_account_event())
420///     .start(&executor)
421///     .await?;
422/// ```
423#[proc_macro_attribute]
424pub fn subscription_all(_attr: TokenStream, item: TokenStream) -> TokenStream {
425    let input = parse_macro_input!(item as ItemFn);
426
427    match subscription_all::subscription_all_next_impl(&input, false) {
428        Ok(tokens) => tokens,
429        Err(e) => e.to_compile_error().into(),
430    }
431}
432
433/// Derive macro for generating cursor structs and trait implementations.
434///
435/// # Example
436///
437/// ```ignore
438/// #[derive(Cursor)]
439/// pub struct AdminView {
440///     #[cursor(ContactAdmin::Id, 1)]
441///     pub id: String,
442///     #[cursor(ContactAdmin::CreatedAt, 2)]
443///     pub created_at: u64,
444/// }
445/// ```
446///
447/// This generates:
448/// - `AdminViewCursor` struct with shortened field names
449/// - `impl evento::cursor::Cursor for AdminView`
450/// - `impl evento::sql::Bind for AdminView`
451#[proc_macro_derive(Cursor, attributes(cursor))]
452pub fn derive_cursor(input: TokenStream) -> TokenStream {
453    let input = parse_macro_input!(input as DeriveInput);
454    match cursor::cursor_impl(&input) {
455        Ok(tokens) => tokens,
456        Err(e) => e.to_compile_error().into(),
457    }
458}
459
460/// Adds a `cursor: String` field and implements `ProjectionCursor`.
461///
462/// This attribute macro transforms a struct to track its position in the event stream.
463/// It automatically adds a `cursor` field and implements the `ProjectionCursor` trait.
464///
465/// # Generated Code
466///
467/// - Adds `pub cursor: String` field
468/// - Adds `Default` and `Clone` derives (preserves existing derives)
469/// - Implements `ProjectionCursor` trait
470///
471/// # Additional Derives
472///
473/// Pass additional derives as arguments:
474///
475/// ```ignore
476/// #[evento::projection(serde::Serialize)]
477/// pub struct MyView { ... }
478/// ```
479///
480/// # Example
481///
482/// ```ignore
483/// #[evento::projection]
484/// #[derive(Debug)]
485/// pub struct MyStruct {
486///     pub id: String,
487///     pub name: String,
488/// }
489///
490/// // Generates:
491/// // #[derive(Default, Clone, Debug)]
492/// // pub struct MyStruct {
493/// //     pub id: String,
494/// //     pub name: String,
495/// //     pub cursor: String,
496/// // }
497/// //
498/// // impl evento::ProjectionCursor for MyStruct { ... }
499/// ```
500#[proc_macro_attribute]
501pub fn projection(attr: TokenStream, item: TokenStream) -> TokenStream {
502    let input = parse_macro_input!(item as DeriveInput);
503    match projection::projection_cursor_impl(attr, &input) {
504        Ok(tokens) => tokens,
505        Err(e) => e.to_compile_error().into(),
506    }
507}