eventsourcing_derive/
lib.rs

1//! # EventSourcing Derive
2//!
3//! Macro implementations for custom derivations for the *eventsourcing* crate
4#![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/// Derives the boilerplate code for a Dispatcher
20#[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/// Derives the boilerplate code for an Event
28#[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}