es_entity_macros/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![cfg_attr(feature = "fail-on-warnings", deny(clippy::all))]
3#![forbid(unsafe_code)]
4
5mod entity;
6mod es_event_context;
7mod event;
8mod query;
9mod repo;
10mod retry_on_concurrent_modification;
11
12use proc_macro::TokenStream;
13use syn::parse_macro_input;
14
15#[proc_macro_derive(EsEvent, attributes(es_event))]
16pub fn es_event_derive(input: TokenStream) -> TokenStream {
17    let ast = parse_macro_input!(input as syn::DeriveInput);
18    match event::derive(ast) {
19        Ok(tokens) => tokens.into(),
20        Err(e) => e.write_errors().into(),
21    }
22}
23
24#[proc_macro_attribute]
25pub fn retry_on_concurrent_modification(args: TokenStream, input: TokenStream) -> TokenStream {
26    let ast = parse_macro_input!(input as syn::ItemFn);
27    match retry_on_concurrent_modification::make(args, ast) {
28        Ok(tokens) => tokens.into(),
29        Err(e) => e.write_errors().into(),
30    }
31}
32
33/// Automatically captures function arguments into the event context.
34///
35/// This attribute macro wraps functions to automatically insert specified arguments
36/// into the current [`EventContext`](es_entity::context::EventContext), making them
37/// available for audit trails when events are persisted.
38///
39/// # Behavior
40///
41/// - **For async functions**: Uses the [`WithEventContext`](es_entity::context::WithEventContext)
42///   trait to propagate context across async boundaries
43/// - **For sync functions**: Uses [`EventContext::fork()`](es_entity::context::EventContext::fork)
44///   to create an isolated child context
45///
46/// # Syntax
47///
48/// ```rust,ignore
49/// #[es_event_context]              // No arguments captured
50/// #[es_event_context(arg1)]         // Capture single argument
51/// #[es_event_context(arg1, arg2)]   // Capture multiple arguments
52/// ```
53///
54/// # Examples
55///
56/// ## Async function with argument capture
57/// ```rust,ignore
58/// use es_entity_macros::es_event_context;
59///
60/// impl UserService {
61///     #[es_event_context(user_id, operation)]
62///     async fn update_user(&self, user_id: UserId, operation: &str, data: UserData) -> Result<()> {
63///         // user_id and operation are automatically added to context
64///         // They will be included when events are persisted
65///         self.repo.update(data).await
66///     }
67/// }
68/// ```
69///
70/// ## Sync function with context isolation
71/// ```rust,ignore
72/// use es_entity_macros::es_event_context;
73///
74/// impl Calculator {
75///     #[es_event_context(transaction_id)]
76///     fn process(&mut self, transaction_id: u64, amount: i64) {
77///         // transaction_id is captured in an isolated context
78///         // Parent context is restored when function exits
79///         self.apply_transaction(amount);
80///     }
81/// }
82/// ```
83///
84/// ## Manual context additions
85/// ```rust,ignore
86/// use es_entity_macros::es_event_context;
87/// use es_entity::context::EventContext;
88///
89/// #[es_event_context(request_id)]
90/// async fn handle_request(request_id: String, data: RequestData) {
91///     // request_id is automatically captured
92///     
93///     // You can still manually add more context
94///     let mut ctx = EventContext::current();
95///     ctx.insert("timestamp", &chrono::Utc::now()).unwrap();
96///     
97///     process_data(data).await;
98/// }
99/// ```
100///
101/// # Context Keys
102///
103/// Arguments are captured using their parameter names as keys. For example,
104/// `user_id: UserId` will be stored with key `"user_id"` in the context.
105///
106/// # See Also
107///
108/// - [`EventContext`](es_entity::context::EventContext) - The context management system
109/// - [`WithEventContext`](es_entity::context::WithEventContext) - Async context propagation
110/// - Event Context chapter in the book for complete usage patterns
111#[proc_macro_attribute]
112pub fn es_event_context(args: TokenStream, input: TokenStream) -> TokenStream {
113    let ast = parse_macro_input!(input as syn::ItemFn);
114    match es_event_context::make(args, ast) {
115        Ok(tokens) => tokens.into(),
116        Err(e) => e.write_errors().into(),
117    }
118}
119
120#[proc_macro_derive(EsEntity, attributes(es_entity))]
121pub fn es_entity_derive(input: TokenStream) -> TokenStream {
122    let ast = parse_macro_input!(input as syn::DeriveInput);
123    match entity::derive(ast) {
124        Ok(tokens) => tokens.into(),
125        Err(e) => e.write_errors().into(),
126    }
127}
128
129#[proc_macro_derive(EsRepo, attributes(es_repo))]
130pub fn es_repo_derive(input: TokenStream) -> TokenStream {
131    let ast = parse_macro_input!(input as syn::DeriveInput);
132    match repo::derive(ast) {
133        Ok(tokens) => tokens.into(),
134        Err(e) => e.write_errors().into(),
135    }
136}
137
138#[proc_macro]
139#[doc(hidden)]
140pub fn expand_es_query(input: TokenStream) -> TokenStream {
141    let input = parse_macro_input!(input as query::QueryInput);
142    match query::expand(input) {
143        Ok(tokens) => tokens.into(),
144        Err(e) => e.write_errors().into(),
145    }
146}