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}