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}