eventsourcing_derive/
lib.rs1#![recursion_limit = "128"]
5
6extern crate proc_macro;
7#[macro_use]
8extern crate quote;
9#[macro_use]
10extern crate syn;
11
12use proc_macro::TokenStream;
13use quote::Tokens;
14use syn::punctuated::Punctuated;
15use syn::synom::Synom;
16use syn::token::Comma;
17use syn::{Data, DataEnum, DeriveInput, Fields, Ident, LitStr, Path, Variant};
18
19#[proc_macro_derive(Dispatcher, attributes(aggregate))]
21pub fn component(input: TokenStream) -> TokenStream {
22 let ast = syn::parse(input).unwrap();
23 let gen = impl_component(&ast);
24 gen.into()
25}
26
27#[proc_macro_derive(Event, attributes(event_type_version, event_source))]
29pub fn component_event(input: TokenStream) -> TokenStream {
30 let ast: DeriveInput = syn::parse(input).unwrap();
31 let gen = match ast.data {
32 Data::Enum(ref data_enum) => impl_component_event(&ast, data_enum),
33 Data::Struct(_) => quote! {
34 panic!("#[derive(Event)] is only defined for enums, not structs")
35 },
36 Data::Union(_) => quote! {
37 panic!("#[derive(Event)] is only defined for enums, not unions")
38 },
39 };
40
41 gen.into()
42}
43
44struct EventSourceAttribute {
45 event_source: LitStr,
46}
47
48struct EventTypeVersionAttribute {
49 event_type_version: Ident,
50}
51
52struct AggregateAttribute {
53 aggregate: Path,
54}
55
56impl Synom for EventSourceAttribute {
57 named!(parse -> Self, map!(
58 parens!(syn!(LitStr)),
59 |(_, event_source)| EventSourceAttribute { event_source }
60 ));
61}
62
63impl Synom for AggregateAttribute {
64 named!(parse -> Self, map!(
65 parens!(syn!(Path)),
66 |(_, aggregate)| AggregateAttribute { aggregate }
67 ));
68}
69
70impl Synom for EventTypeVersionAttribute {
71 named!(parse -> Self, map!(
72 parens!(syn!(Ident)),
73 |(_, event_type_version) | EventTypeVersionAttribute { event_type_version }
74 ));
75}
76
77fn impl_component_event(ast: &DeriveInput, data_enum: &DataEnum) -> Tokens {
78 let name = &ast.ident;
79 let variants = &data_enum.variants;
80 let (impl_generics, _ty_generics, where_clause) = ast.generics.split_for_impl();
81 let event_type_version = ast
82 .attrs
83 .iter()
84 .find(|attr| attr.path.segments[0].ident == "event_type_version")
85 .map(|attr| {
86 syn::parse2::<EventTypeVersionAttribute>(attr.tts.clone())
87 .unwrap()
88 .event_type_version
89 })
90 .unwrap_or_else(|| parse_quote!(NoSchemaVersion));
91
92 let event_source = ast
93 .attrs
94 .iter()
95 .find(|attr| attr.path.segments[0].ident == "event_source")
96 .map(|attr| {
97 syn::parse2::<EventSourceAttribute>(attr.tts.clone())
98 .unwrap()
99 .event_source
100 })
101 .unwrap_or_else(|| parse_quote!(NoEventSource));
102
103 let event_matches = generate_event_matches(&name, &variants);
104
105 quote! {
106 impl #impl_generics ::eventsourcing::Event for #name #where_clause {
107 fn event_type_version(&self) -> &str {
108 #event_type_version
109 }
110
111 fn event_source(&self) -> &str {
112 #event_source
113 }
114
115 fn event_type(&self) -> &str {
116 match self {
117 #(#event_matches)*
118 }
119 }
120 }
121 #[cfg(feature = "orgeventstore")]
122 impl From<::eventsourcing::cloudevents::CloudEvent> for #name {
123 fn from(__source: ::eventsourcing::cloudevents::CloudEvent) -> Self {
124 ::serde_json::from_str(&::serde_json::to_string(&__source.data).unwrap()).unwrap()
125 }
126 }
127 }
128}
129
130fn generate_event_matches(
131 name: &Ident,
132 variants: &Punctuated<Variant, Comma>,
133) -> Vec<quote::Tokens> {
134 let mut result = Vec::new();
135 for (_idx, variant) in variants.iter().enumerate() {
136 let id = &variant.ident;
137 let et_name = event_type_name(name, id);
138 let new = match variant.fields {
139 Fields::Unit => quote! {
140 #name::#id => #et_name,
141 },
142 Fields::Unnamed(ref fields) => {
143 let idents: Vec<_> = fields.unnamed.pairs().map(|p| p.value().ident).collect();
144 quote! {
145 #name::#id( #(_#idents,)* ) => #et_name,
146 }
147 }
148 Fields::Named(ref fields) => {
149 let idents: Vec<_> = fields.named.pairs().map(|p| p.value().ident).collect();
150 quote! {
151 #name::#id { #(#idents: _,)* } => #et_name,
152 }
153 }
154 };
155 result.push(new);
156 }
157 result
158}
159
160fn event_type_name(name: &Ident, variant_id: &Ident) -> String {
161 let name_s = name.to_string().to_lowercase();
162 let variant_s = variant_id.to_string().to_lowercase();
163 format!("{}.{}", name_s, variant_s)
164}
165
166fn impl_component(ast: &DeriveInput) -> Tokens {
167 let name = &ast.ident;
168 let (impl_generics, _ty_generics, where_clause) = ast.generics.split_for_impl();
169
170 let aggregate = ast
171 .attrs
172 .iter()
173 .find(|attr| attr.path.segments[0].ident == "aggregate")
174 .map(|attr| {
175 syn::parse2::<AggregateAttribute>(attr.tts.clone())
176 .unwrap()
177 .aggregate
178 })
179 .unwrap_or_else(|| parse_quote!(NoAggregate));
180
181 quote! {
182 impl #impl_generics ::eventsourcing::Dispatcher for #name #where_clause {
183 type Aggregate = #aggregate;
184 type Event = <#aggregate as Aggregate>::Event;
185 type Command = <#aggregate as Aggregate>::Command;
186 type State = <#aggregate as Aggregate>::State;
187
188 fn dispatch(
189 state: &Self::State,
190 cmd: &Self::Command,
191 store: &impl ::eventsourcing::eventstore::EventStore,
192 stream: &str,
193 ) -> Vec<Result<::eventsourcing::cloudevents::CloudEvent>> {
194 match Self::Aggregate::handle_command(state, cmd) {
195 Ok(evts) => evts.into_iter().map(|evt| store.append(evt, stream)).collect(),
196 Err(e) => vec![Err(e)],
197 }
198 }
199 }
200 }
201}