eventide_macros/lib.rs
1//! Procedural macros for the `eventide` Domain-Driven Design / CQRS toolkit.
2//!
3//! This crate provides four attribute macros that remove boilerplate when
4//! modelling DDD building blocks in Rust:
5//!
6//! | Macro | Applies to | Purpose |
7//! |--------------------|-----------------------|------------------------------------------------------------------------|
8//! | [`entity`] | `struct` (named) | Mark a type as a DDD entity / aggregate root. |
9//! | [`entity_id`] | tuple `struct` | Newtype wrapper around a primitive ID with a rich set of conversions. |
10//! | [`domain_event`] | `enum` (named fields) | Tag an enum as the event family for an aggregate. |
11//! | [`value_object`] | `struct` or `enum` | Add the standard derive set expected of immutable value objects. |
12//!
13//! Each macro lives in its own module; this crate root only re-exports the
14//! procedural-macro entry points and forwards to the per-macro `expand`
15//! function. See the documentation on each macro for the supported
16//! attribute arguments and concrete examples.
17mod domain_event;
18mod entity;
19mod entity_id;
20mod utils;
21mod value_object;
22
23use proc_macro::TokenStream;
24
25/// Mark a struct as a DDD entity / aggregate root.
26///
27/// The macro guarantees that the annotated struct has the two fields every
28/// `eventide_domain::entity::Entity` implementation needs:
29///
30/// * `id: <IdType>` — the entity's identity.
31/// * `version: ::eventide_domain::value_object::Version` — the optimistic-
32/// concurrency version used during event-sourced rehydration.
33///
34/// If either field is missing it is inserted; if both are present they are
35/// repositioned so they appear at the top of the struct (in that order),
36/// keeping the layout consistent across all entities. The macro then
37/// generates an `impl Entity for ...` block exposing `new`, `id` and
38/// `version`.
39///
40/// A normalised `derive` attribute is also produced. By default this
41/// includes `Debug`, `Default`, `serde::Serialize` and `serde::Deserialize`;
42/// any derives the user already wrote are merged in (de-duplicated against
43/// the standard set, e.g. `Serialize` and `serde::Serialize` collapse).
44///
45/// # Attribute arguments
46///
47/// `#[entity(id = <Type>, debug = <bool>)]`
48///
49/// * `id` — type used for the `id` field. Defaults to `String`.
50/// * `debug` — when `false` the macro will *not* derive `Debug`, so callers
51/// can provide a custom `Debug` impl. Defaults to `true`.
52///
53/// Both keys are optional and may appear in any order.
54///
55/// # Examples
56///
57/// Minimal usage with a `String` identifier:
58///
59/// ```ignore
60/// use eventide_macros::entity;
61///
62/// #[entity]
63/// struct Account {
64/// name: String,
65/// balance: i64,
66/// }
67/// ```
68///
69/// With a custom newtype identifier:
70///
71/// ```ignore
72/// use eventide_macros::{entity, entity_id};
73/// use uuid::Uuid;
74///
75/// #[entity_id]
76/// struct UserId(Uuid);
77///
78/// #[entity(id = UserId)]
79/// struct User {
80/// name: String,
81/// }
82/// ```
83///
84/// Suppressing the derived `Debug` to provide a custom impl:
85///
86/// ```ignore
87/// use eventide_macros::entity;
88///
89/// #[entity(id = String, debug = false)]
90/// struct Secret {
91/// value: String,
92/// }
93///
94/// impl std::fmt::Debug for Secret {
95/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96/// f.debug_struct("Secret").field("value", &"<redacted>").finish()
97/// }
98/// }
99/// ```
100#[proc_macro_attribute]
101pub fn entity(attr: TokenStream, item: TokenStream) -> TokenStream {
102 entity::expand(attr, item)
103}
104
105/// Turn a single-field tuple struct into a fully-featured entity-ID newtype.
106///
107/// Applied to `struct Foo(Inner);` (exactly one unnamed field), this macro
108/// generates the standard derive set plus the conversions and helpers users
109/// expect from a strongly-typed identifier:
110///
111/// * Default derives: `Default`, `Clone`, `Copy`, `serde::Serialize`,
112/// `serde::Deserialize`, `PartialEq`, `Eq`, `Hash`, and (by default) `Debug`.
113/// User-supplied derives are merged in.
114/// * `pub fn new(value: Inner) -> Self`
115/// * `impl FromStr` (forwarding to the inner type's `FromStr`).
116/// * `impl Display` (forwarding to the inner type's `Display`).
117/// * `impl AsRef<Inner>` and `impl AsMut<Inner>`.
118/// * `impl From<Inner>`/`impl From<&Inner>` and the symmetric
119/// `impl From<Self>`/`impl From<&Self> for Inner` conversions.
120///
121/// # Attribute arguments
122///
123/// `#[entity_id(debug = <bool>)]`
124///
125/// * `debug` — disable the auto-derived `Debug` (defaults to `true`) so
126/// callers can hand-write a redacting or otherwise customised impl.
127///
128/// # Important
129///
130/// Because the macro derives `Copy`, the inner type **must implement
131/// `Copy`** (e.g. `Uuid`, `u64`, `i64`). Wrappers around `String` are not
132/// supported through this macro.
133///
134/// # Examples
135///
136/// ```ignore
137/// use eventide_macros::entity_id;
138/// use uuid::Uuid;
139///
140/// #[entity_id]
141/// struct OrderId(Uuid);
142///
143/// let id = OrderId::new(Uuid::new_v4());
144/// let copy = id; // `Copy` works.
145/// let _ = format!("{} / {:?}", id, id); // `Display` and `Debug` both work.
146/// ```
147///
148/// Custom `Debug` impl:
149///
150/// ```ignore
151/// use eventide_macros::entity_id;
152/// use uuid::Uuid;
153///
154/// #[entity_id(debug = false)]
155/// struct ProfileId(Uuid);
156///
157/// impl std::fmt::Debug for ProfileId {
158/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159/// write!(f, "ProfileId(<redacted>)")
160/// }
161/// }
162/// ```
163#[proc_macro_attribute]
164pub fn entity_id(attr: TokenStream, item: TokenStream) -> TokenStream {
165 entity_id::expand(attr, item)
166}
167
168/// Tag an enum as the domain-event family for an aggregate.
169///
170/// The macro is applied to an enum where every variant uses **named fields**
171/// (`Variant { .. }`). For each variant it ensures the presence of:
172///
173/// * `id: <IdType>` — the unique event identifier.
174/// * `aggregate_version: ::eventide_domain::value_object::Version` — the
175/// aggregate version this event belongs to.
176///
177/// Missing fields are appended (existing variant fields keep their relative
178/// order). The macro then generates an `impl
179/// ::eventide_domain::domain_event::DomainEvent` whose four methods —
180/// `event_id`, `event_type`, `event_version`, `aggregate_version` — are
181/// implemented as `match` expressions over the variants.
182///
183/// A normalised `derive` set (`Debug`, `Clone`, `PartialEq`,
184/// `serde::Serialize`, `serde::Deserialize`) is merged with any derives the
185/// user already wrote.
186///
187/// # Enum-level attribute arguments
188///
189/// `#[domain_event(id = <Type>, version = <int>)]`
190///
191/// * `id` — the type used for every variant's `id` field. Defaults to
192/// `String`.
193/// * `version` — the default `event_version` returned for variants that do
194/// not override it. Defaults to `1`.
195///
196/// # Per-variant overrides
197///
198/// Every variant may carry an optional `#[event(...)]` attribute:
199///
200/// `#[event(event_type = "...", event_version = N)]`
201///
202/// * `event_type` — string returned from `event_type()` for that variant.
203/// When omitted it falls back to `"<EnumName>.<VariantName>"`.
204/// * `event_version` — integer returned from `event_version()` for that
205/// variant. When omitted it falls back to the enum-level `version`.
206///
207/// The legacy syntax `#[event_type = "..."]` / `#[event_version = N]` is
208/// **rejected** with a compile error pointing users to the unified
209/// `#[event(...)]` form.
210///
211/// # Examples
212///
213/// ```ignore
214/// use eventide_macros::domain_event;
215///
216/// #[domain_event(version = 1)]
217/// enum BankEvent {
218/// #[event(event_type = "bank.opened")]
219/// Opened { name: String },
220///
221/// // Override only the version; the event_type defaults to
222/// // "BankEvent.Renamed".
223/// #[event(event_version = 2)]
224/// Renamed { to: String },
225/// }
226/// ```
227///
228/// Using a custom identifier type at the enum level:
229///
230/// ```ignore
231/// use eventide_macros::domain_event;
232///
233/// #[domain_event(id = String, version = 1)]
234/// enum UserEvent {
235/// Created { name: String },
236/// }
237/// ```
238#[proc_macro_attribute]
239pub fn domain_event(attr: TokenStream, item: TokenStream) -> TokenStream {
240 domain_event::expand(attr, item)
241}
242
243/// Add the conventional derive set for an immutable value object.
244///
245/// Applied to either a `struct` (named or tuple) or an `enum`. The macro
246/// merges the user's existing derives with: `Default`, `Clone`,
247/// `serde::Serialize`, `serde::Deserialize`, `PartialEq`, `Eq`, and (by
248/// default) `Debug`. Unlike [`entity`] or [`domain_event`] no fields,
249/// methods or trait impls are generated — value objects are intentionally
250/// minimal pieces of data.
251///
252/// # Attribute arguments
253///
254/// `#[value_object(debug = <bool>)]`
255///
256/// * `debug` — disable the auto-derived `Debug` (defaults to `true`) when
257/// callers want to provide a manual implementation (e.g. to redact
258/// sensitive content).
259///
260/// # Examples
261///
262/// On a struct:
263///
264/// ```ignore
265/// use eventide_macros::value_object;
266///
267/// #[value_object]
268/// struct Money {
269/// amount: i64,
270/// currency: String,
271/// }
272/// ```
273///
274/// On an enum (note the `#[default]` variant required by the derived
275/// `Default`):
276///
277/// ```ignore
278/// use eventide_macros::value_object;
279///
280/// #[value_object]
281/// enum Level {
282/// #[default]
283/// Low,
284/// High,
285/// }
286/// ```
287///
288/// Disabling auto-`Debug` to write a redacting impl:
289///
290/// ```ignore
291/// use eventide_macros::value_object;
292///
293/// #[value_object(debug = false)]
294/// struct ApiKey(String);
295///
296/// impl std::fmt::Debug for ApiKey {
297/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298/// f.write_str("ApiKey(<redacted>)")
299/// }
300/// }
301/// ```
302#[proc_macro_attribute]
303pub fn value_object(attr: TokenStream, item: TokenStream) -> TokenStream {
304 value_object::expand(attr, item)
305}