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}