disintegrate_macros/
lib.rs

1mod event;
2mod state_query;
3mod symbol;
4
5extern crate proc_macro;
6
7use proc_macro::TokenStream;
8use proc_macro2::{Ident, TokenStream as TokenStream2};
9
10use syn::{parse_macro_input, DeriveInput};
11
12/// Derives the `Event` trait for an enum, allowing it to be used as an event in Disintegrate.
13///
14/// The `Event` trait is used to mark an enum as an event type in Disintegrate. By deriving this
15/// trait, the enum gains the necessary functionality to be used as an event.
16///
17/// The `Event` trait can be customized using attributes. The `id` attribute can be used to specify
18/// the domain identifier of an event, while the `stream` attribute can be used to stream related
19/// events together.
20///
21/// # Example
22///
23/// ```rust
24/// use disintegrate::Event;
25///
26/// #[derive(Event)]
27/// #[stream(UserEvent, [UserRegistered, UserUpdated])]
28/// #[stream(OrderEvent, [OrderCreated, OrderCancelled])]
29/// enum DomainEvent{
30///     UserCreated {
31///         #[id]
32///         user_id: String,
33///         name: String,
34///         email: String,
35///     },
36///     UserUpdated {
37///         #[id]
38///         user_id: String,
39///         email: String,
40///     },
41///     OrderCreated {
42///         #[id]
43///         order_id: String,
44///         amount: u32
45///     },
46///     OrderCancelled {
47///         #[id]
48///         order_id: String
49///     },
50/// }
51/// ```
52///
53/// In this example, the `OrderEvent` enum is marked as an event by deriving the `Event` trait. The
54/// `#[stream]` attribute specifies the event stream name and the list of variants to include in the stream, while the `#[id]` attribute is used
55/// to specify the domain identifiers of each variant.
56#[proc_macro_derive(Event, attributes(stream, id))]
57pub fn event(input: TokenStream) -> TokenStream {
58    let ast = parse_macro_input!(input as DeriveInput);
59    event::event_inner(&ast)
60        .unwrap_or_else(syn::Error::into_compile_error)
61        .into()
62}
63
64/// Derives the `StateQuery` trait for a struct, enabling its use as a state query in Disintegrate.
65///
66/// The `state_query` attribute is mandatory and must include the event type associated with the state query.
67/// Additionally, the `id` attribute can be utilized to specify the domain identifier of a state query. It is employed
68/// in generating a stream query for the state, querying for the event specified in the `state_query`
69/// attribute, with the identifiers marked in `or`.
70///
71/// It is also possible to rename a state using the `rename` argument in the `state_query` attribute. This feature is beneficial
72/// for snapshotting, and the name specified in `rename` is used to identify the snapshot.
73///
74/// # Example
75///
76/// ```rust
77/// # use disintegrate::Event;
78/// # #[derive(Event, Clone)]
79/// # enum DomainEvent{
80/// #    UserCreated {
81/// #         #[id]
82/// #         user_id: String,
83/// #     },
84/// # }
85///
86/// use disintegrate::StateQuery;
87///
88/// #[derive(StateQuery, Clone)]
89/// #[state_query(DomainEvent, rename = "user-query-v1")] // Rename the state for snapshotting
90/// struct UserStateQuery {
91///     #[id]
92///     user_id: String,
93///     name: String
94/// }
95/// ```
96///
97/// In this example, the `UserStateQuery` struct is annotated with the `StateQuery` derive,
98/// indicating its role as a state query. The `#[state_query]` attribute specifies the associated event type,
99/// and the `#[id]` attribute is used to define the domain identifiers. The `#[state_query]` attribute with `rename`
100/// renames the state to 'user-query-v1' for snapshotting purposes.
101#[proc_macro_derive(StateQuery, attributes(state_query, id))]
102pub fn state_query(input: TokenStream) -> TokenStream {
103    let ast = parse_macro_input!(input as DeriveInput);
104    state_query::state_query_inner(&ast)
105        .unwrap_or_else(syn::Error::into_compile_error)
106        .into()
107}
108
109fn reserved_identifier_names(identifiers_fields: &[&Ident]) -> Option<TokenStream2> {
110    const RESERVED_NAMES: &[&str] = &["event_id", "payload", "event_type", "inserted_at"];
111
112    identifiers_fields
113        .iter()
114        .find(|id| RESERVED_NAMES.contains(&id.to_string().as_str()))
115        .map(|id| {
116            syn::Error::new(
117                id.span(),
118                "Reserved domain identifier name. Please use a different name",
119            )
120            .to_compile_error()
121        })
122}