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