1mod all_variants;
8mod error;
9mod event_payload;
10mod operation;
11
12use proc_macro::TokenStream;
13use quote::quote;
14use std::collections::HashSet;
15use syn::{
16 parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, Ident, LitInt, Path,
17};
18
19#[proc_macro_derive(EventPayload, attributes(batpak))]
24pub fn derive_event_payload(input: TokenStream) -> TokenStream {
25 let input = parse_macro_input!(input as DeriveInput);
26 match event_payload::expand(&input) {
27 Ok(ts) => ts.into(),
28 Err(e) => e.to_compile_error().into(),
29 }
30}
31
32#[proc_macro_derive(Error, attributes(error, source, from))]
40pub fn derive_error(input: TokenStream) -> TokenStream {
41 let input = parse_macro_input!(input as DeriveInput);
42 match error::expand(&input) {
43 Ok(ts) => ts.into(),
44 Err(e) => e.to_compile_error().into(),
45 }
46}
47
48#[proc_macro_derive(AllVariants)]
51pub fn derive_all_variants(input: TokenStream) -> TokenStream {
52 let input = parse_macro_input!(input as DeriveInput);
53 match all_variants::expand(&input) {
54 Ok(ts) => ts.into(),
55 Err(e) => e.to_compile_error().into(),
56 }
57}
58
59#[proc_macro_attribute]
64pub fn operation(attr: TokenStream, item: TokenStream) -> TokenStream {
65 let args = parse_macro_input!(attr as operation::OperationArgs);
66 let function = parse_macro_input!(item as syn::ItemFn);
67 match operation::expand_operation(args, &function) {
68 Ok(tokens) => tokens.into(),
69 Err(error) => error.to_compile_error().into(),
70 }
71}
72
73#[proc_macro_derive(MultiEventReactor, attributes(batpak))]
92pub fn derive_multi_event_reactor(input: TokenStream) -> TokenStream {
93 let input = parse_macro_input!(input as DeriveInput);
94 match expand_multi_event_reactor(&input) {
95 Ok(ts) => ts.into(),
96 Err(e) => e.to_compile_error().into(),
97 }
98}
99
100#[proc_macro_derive(EventSourced, attributes(batpak))]
145pub fn derive_event_sourced(input: TokenStream) -> TokenStream {
146 let input = parse_macro_input!(input as DeriveInput);
147 match expand_event_sourced(&input) {
148 Ok(ts) => ts.into(),
149 Err(e) => e.to_compile_error().into(),
150 }
151}
152
153struct EventBinding {
158 event: Path,
159 handler: Ident,
160}
161
162enum BatpakAttrKind {
168 Config {
169 input: Option<Path>,
170 cache_version: Option<LitInt>,
171 state_max_cardinality: Option<LitInt>,
172 error: Option<Path>,
173 },
174 Event(EventBinding),
175}
176
177#[derive(Default)]
178struct BatpakAttrParts {
179 input: Option<Path>,
180 cache_version: Option<LitInt>,
181 state_max_cardinality: Option<LitInt>,
182 error_ty: Option<Path>,
183 event: Option<Path>,
184 handler: Option<Ident>,
185}
186
187impl BatpakAttrParts {
188 fn set_nested(&mut self, meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<()> {
189 let key = meta.path.get_ident().ok_or_else(|| {
190 meta.error("expected `input`, `cache_version`, `state_max_cardinality`, `error`, `event`, or `handler`")
191 })?;
192 let key_name = key.to_string();
193 if self.set_config_nested(key_name.as_str(), meta)? {
194 return Ok(());
195 }
196 if self.set_event_nested(key_name.as_str(), meta)? {
197 return Ok(());
198 }
199 Err(meta.error(format!(
200 "unknown key `{key_name}`, expected `input`, `cache_version`, `state_max_cardinality`, `error`, `event`, or `handler`"
201 )))
202 }
203
204 fn set_config_nested(
205 &mut self,
206 key: &str,
207 meta: &syn::meta::ParseNestedMeta<'_>,
208 ) -> syn::Result<bool> {
209 match key {
210 "input" => {
211 if self.input.is_some() {
212 return Err(meta.error("duplicate `input` key within attribute"));
213 }
214 self.input = Some(meta.value()?.parse::<Path>()?);
215 }
216 "cache_version" => {
217 if self.cache_version.is_some() {
218 return Err(meta.error("duplicate `cache_version` key within attribute"));
219 }
220 self.cache_version = Some(meta.value()?.parse::<LitInt>()?);
221 }
222 "state_max_cardinality" => {
223 if self.state_max_cardinality.is_some() {
224 return Err(
225 meta.error("duplicate `state_max_cardinality` key within attribute")
226 );
227 }
228 self.state_max_cardinality = Some(meta.value()?.parse::<LitInt>()?);
229 }
230 "error" => {
231 if self.error_ty.is_some() {
232 return Err(meta.error("duplicate `error` key within attribute"));
233 }
234 self.error_ty = Some(meta.value()?.parse::<Path>()?);
235 }
236 _ => return Ok(false),
237 }
238 Ok(true)
239 }
240
241 fn set_event_nested(
242 &mut self,
243 key: &str,
244 meta: &syn::meta::ParseNestedMeta<'_>,
245 ) -> syn::Result<bool> {
246 match key {
247 "event" => {
248 if self.event.is_some() {
249 return Err(meta.error("duplicate `event` key within attribute"));
250 }
251 self.event = Some(meta.value()?.parse::<Path>()?);
252 }
253 "handler" => {
254 if self.handler.is_some() {
255 return Err(meta.error("duplicate `handler` key within attribute"));
256 }
257 self.handler = Some(meta.value()?.parse::<Ident>()?);
258 }
259 _ => return Ok(false),
260 }
261 Ok(true)
262 }
263
264 fn finish(self, attr: &Attribute) -> syn::Result<BatpakAttrKind> {
265 let has_config = self.input.is_some()
266 || self.cache_version.is_some()
267 || self.state_max_cardinality.is_some()
268 || self.error_ty.is_some();
269 let has_event = self.event.is_some() || self.handler.is_some();
270
271 if has_config && has_event {
272 return Err(syn::Error::new(
273 attr.span(),
274 "`#[batpak(...)]` attribute must contain either config keys \
275 (`input`, `cache_version`, `state_max_cardinality`, `error`) or an event-binding pair (`event`, `handler`), not both",
276 ));
277 }
278
279 if has_event {
280 let event = self.event.ok_or_else(|| {
281 syn::Error::new(
282 attr.span(),
283 "event-binding attribute is missing `event = <PayloadType>`",
284 )
285 })?;
286 let handler = self.handler.ok_or_else(|| {
287 syn::Error::new(
288 attr.span(),
289 "event-binding attribute is missing `handler = <fn_name>`",
290 )
291 })?;
292 return Ok(BatpakAttrKind::Event(EventBinding { event, handler }));
293 }
294
295 if !has_config {
296 return Err(syn::Error::new(
297 attr.span(),
298 "`#[batpak(...)]` must contain at least one key: `input`, `cache_version`, `state_max_cardinality`, `error`, or the `event`/`handler` pair",
299 ));
300 }
301 Ok(BatpakAttrKind::Config {
302 input: self.input,
303 cache_version: self.cache_version,
304 state_max_cardinality: self.state_max_cardinality,
305 error: self.error_ty,
306 })
307 }
308}
309
310fn classify_batpak_attr(attr: &Attribute) -> syn::Result<BatpakAttrKind> {
311 let mut parts = BatpakAttrParts::default();
312 attr.parse_nested_meta(|meta| parts.set_nested(&meta))?;
313 parts.finish(attr)
314}
315
316fn ensure_named_field_struct(input: &DeriveInput, derive_name: &str) -> syn::Result<()> {
317 match &input.data {
318 Data::Struct(s) => match &s.fields {
319 Fields::Named(_) => Ok(()),
320 Fields::Unnamed(f) => Err(syn::Error::new(
321 f.span(),
322 format!(
323 "#[derive({derive_name})] requires a named-field struct; tuple structs are not supported"
324 ),
325 )),
326 Fields::Unit => Err(syn::Error::new(
327 input.ident.span(),
328 format!(
329 "#[derive({derive_name})] requires a named-field struct; unit structs are not supported"
330 ),
331 )),
332 },
333 Data::Enum(e) => Err(syn::Error::new(
334 e.enum_token.span,
335 format!("#[derive({derive_name})] requires a named-field struct; enums are not supported"),
336 )),
337 Data::Union(u) => Err(syn::Error::new(
338 u.union_token.span,
339 format!(
340 "#[derive({derive_name})] requires a named-field struct; unions are not supported"
341 ),
342 )),
343 }
344}
345
346struct EventSourcedDeriveAttrs {
347 input_path: Path,
348 cache_version_lit: Option<LitInt>,
349 state_max_cardinality_lit: Option<LitInt>,
350 bindings: Vec<EventBinding>,
351}
352
353fn collect_event_sourced_attrs(input: &DeriveInput) -> syn::Result<EventSourcedDeriveAttrs> {
354 let batpak_attrs: Vec<&Attribute> = input
355 .attrs
356 .iter()
357 .filter(|a| a.path().is_ident("batpak"))
358 .collect();
359
360 if batpak_attrs.is_empty() {
361 return Err(syn::Error::new(
362 input.ident.span(),
363 "#[derive(EventSourced)] requires at least one `#[batpak(input = <Lane>)]` attribute",
364 ));
365 }
366
367 let mut input_path: Option<Path> = None;
368 let mut cache_version_lit: Option<LitInt> = None;
369 let mut state_max_cardinality_lit: Option<LitInt> = None;
370 let mut bindings: Vec<EventBinding> = Vec::new();
371 let mut seen_events: HashSet<String> = HashSet::new();
372
373 for attr in &batpak_attrs {
374 match classify_batpak_attr(attr)? {
375 BatpakAttrKind::Config {
376 input: attr_input,
377 cache_version: attr_cache,
378 state_max_cardinality: attr_state_max,
379 error: attr_error,
380 } => {
381 collect_event_sourced_config(
382 &mut input_path,
383 &mut cache_version_lit,
384 &mut state_max_cardinality_lit,
385 attr_input,
386 attr_cache,
387 attr_state_max,
388 attr_error,
389 )?;
390 }
391 BatpakAttrKind::Event(binding) => {
392 collect_unique_event_binding(
393 &mut bindings,
394 &mut seen_events,
395 binding,
396 "projection",
397 )?;
398 }
399 }
400 }
401
402 let input_path = input_path.ok_or_else(|| {
403 syn::Error::new(
404 input.ident.span(),
405 "#[derive(EventSourced)] requires `#[batpak(input = <Lane>)]` — e.g. `input = JsonValueInput` or `input = RawMsgpackInput`",
406 )
407 })?;
408
409 if bindings.is_empty() {
410 return Err(syn::Error::new(
411 input.ident.span(),
412 "`#[derive(EventSourced)]` requires at least one `#[batpak(event = T, handler = h)]` binding",
413 ));
414 }
415
416 Ok(EventSourcedDeriveAttrs {
417 input_path,
418 cache_version_lit,
419 state_max_cardinality_lit,
420 bindings,
421 })
422}
423
424fn collect_event_sourced_config(
425 input_path: &mut Option<Path>,
426 cache_version_lit: &mut Option<LitInt>,
427 state_max_cardinality_lit: &mut Option<LitInt>,
428 attr_input: Option<Path>,
429 attr_cache: Option<LitInt>,
430 attr_state_max: Option<LitInt>,
431 attr_error: Option<Path>,
432) -> syn::Result<()> {
433 if let Some(path) = attr_error {
434 return Err(syn::Error::new(
435 path.span(),
436 "`error` is not valid on `#[derive(EventSourced)]` — projections do not have an associated error type",
437 ));
438 }
439 if let Some(path) = attr_input {
440 if input_path.is_some() {
441 return Err(syn::Error::new(
442 path.span(),
443 "duplicate `input =` across `#[batpak(...)]` config attributes — `input` must appear exactly once",
444 ));
445 }
446 *input_path = Some(path);
447 }
448 if let Some(lit) = attr_cache {
449 if cache_version_lit.is_some() {
450 return Err(syn::Error::new(
451 lit.span(),
452 "duplicate `cache_version =` across `#[batpak(...)]` config attributes",
453 ));
454 }
455 *cache_version_lit = Some(lit);
456 }
457 if let Some(lit) = attr_state_max {
458 if state_max_cardinality_lit.is_some() {
459 return Err(syn::Error::new(
460 lit.span(),
461 "duplicate `state_max_cardinality =` across `#[batpak(...)]` config attributes",
462 ));
463 }
464 *state_max_cardinality_lit = Some(lit);
465 }
466 Ok(())
467}
468
469fn collect_unique_event_binding(
470 bindings: &mut Vec<EventBinding>,
471 seen_events: &mut HashSet<String>,
472 binding: EventBinding,
473 owner: &str,
474) -> syn::Result<()> {
475 require_single_segment_event_path(&binding.event)?;
476 let key = binding.event.to_token_stream_string();
477 if !seen_events.insert(key) {
478 return Err(syn::Error::new(
479 binding.event.span(),
480 format!(
481 "duplicate `event = X` — each payload type may be bound to exactly one handler per {owner}"
482 ),
483 ));
484 }
485 bindings.push(binding);
486 Ok(())
487}
488
489fn expand_event_sourced(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
490 ensure_named_field_struct(input, "EventSourced")?;
491
492 let attrs = collect_event_sourced_attrs(input)?;
493 let input_path = attrs.input_path;
494 let cache_version_lit = attrs.cache_version_lit;
495 let state_max_cardinality_lit = attrs.state_max_cardinality_lit;
496 let bindings = attrs.bindings;
497
498 let cache_version_value: u64 = match &cache_version_lit {
500 Some(lit) => lit.base10_parse::<u64>()?,
501 None => 0u64,
502 };
503
504 let ident = &input.ident;
506 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
507
508 let state_contract_impl = match &state_max_cardinality_lit {
509 Some(lit) => {
510 let state_max_cardinality_value = lit.base10_parse::<u64>()?;
511 if state_max_cardinality_value != 1 {
516 return Err(syn::Error::new_spanned(
517 lit,
518 "#[derive(EventSourced)] supports only single-aggregate state (n = 1); \
519 implement EventSourced by hand with a real `state_extent()` for multi-key state",
520 ));
521 }
522 quote! {
523 const STATE_CONTRACT: ::batpak::event::ProjectionStateContract =
524 ::batpak::event::ProjectionStateContract::Bounded {
525 key_space: ::core::concat!(
526 ::core::module_path!(),
527 "::",
528 ::core::stringify!(#ident)
529 ),
530 max_cardinality: #state_max_cardinality_value,
531 retention_policy: "derive-event-sourced-state-object",
532 compaction_policy: "projection-cache-overwrite",
533 checkpoint_policy: "projection-cache",
534 };
535
536 fn state_extent(&self) -> ::batpak::event::StateExtent {
537 let _ = self;
538 ::batpak::event::StateExtent::cardinality(
539 1,
540 ::batpak::event::StateExtentCost::ConstantTime,
541 )
542 }
543 }
544 }
545 None => quote! {},
546 };
547
548 let arms: Vec<proc_macro2::TokenStream> = bindings
553 .iter()
554 .map(|b| {
555 let event_ty = &b.event;
556 let handler_fn = &b.handler;
557 quote! {
558 match ::batpak::event::DecodeTyped::route_typed::<#event_ty>(event) {
563 ::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
564 self.#handler_fn(&__p);
565 return;
566 }
567 ::core::result::Result::Ok(::core::option::Option::None) => {}
568 ::core::result::Result::Err(__e) => {
569 ::core::panic!(
570 "EventSourced: decode failed for matched kind {}: {}",
571 ::core::stringify!(#event_ty),
572 __e
573 );
574 }
575 }
576 }
577 })
578 .collect();
579
580 let kind_exprs: Vec<proc_macro2::TokenStream> = bindings
582 .iter()
583 .map(|b| {
584 let event_ty = &b.event;
585 quote! {
586 <#event_ty as ::batpak::event::EventPayload>::KIND
587 }
588 })
589 .collect();
590 let kind_count = bindings.len();
591
592 let handler_checks: Vec<proc_macro2::TokenStream> = bindings
599 .iter()
600 .map(|b| {
601 let event_ty = &b.event;
602 let handler_fn = &b.handler;
603 quote! {
604 let _: fn(&mut Self, &#event_ty) = Self::#handler_fn;
605 }
606 })
607 .collect();
608
609 let input_assertion = {
614 quote! {
615 const _: fn() = || {
616 fn __batpak_assert_projection_input<T: ::batpak::event::ProjectionInput>() {}
617 __batpak_assert_projection_input::<#input_path>();
618 };
619 }
620 };
621
622 Ok(quote! {
623 #input_assertion
624
625 impl #impl_generics ::batpak::event::EventSourced for #ident #ty_generics #where_clause {
626 type Input = #input_path;
627
628 #state_contract_impl
629
630 fn from_events(
631 events: &[::batpak::event::ProjectionEvent<Self>],
632 ) -> ::core::option::Option<Self> {
633 if events.is_empty() {
634 return ::core::option::Option::None;
635 }
636 let mut state: Self = ::core::default::Default::default();
637 for __ev in events {
638 state.apply_event(__ev);
639 }
640 ::core::option::Option::Some(state)
641 }
642
643 fn apply_event(&mut self, event: &::batpak::event::ProjectionEvent<Self>) {
644 #(#handler_checks)*
645 #(#arms)*
650 let _ = event;
652 }
653
654 fn relevant_event_kinds() -> &'static [::batpak::event::EventKind] {
655 static KINDS: [::batpak::event::EventKind; #kind_count] = [
656 #(#kind_exprs),*
657 ];
658 &KINDS
659 }
660
661 fn schema_version() -> u64 {
662 #cache_version_value
666 }
667 }
668 })
669}
670
671trait ToTokenStreamString {
672 fn to_token_stream_string(&self) -> String;
673}
674
675impl ToTokenStreamString for Path {
676 fn to_token_stream_string(&self) -> String {
677 quote!(#self).to_string()
678 }
679}
680
681fn require_single_segment_event_path(path: &Path) -> syn::Result<()> {
691 if path.leading_colon.is_some() || path.segments.len() != 1 {
692 return Err(syn::Error::new_spanned(
693 path,
694 "event type must be named by its in-scope single-segment name — use a `use` import if the type is in another module",
695 ));
696 }
697 Ok(())
698}
699
700fn expand_multi_event_reactor(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
703 ensure_named_field_struct(input, "MultiEventReactor")?;
704
705 let batpak_attrs: Vec<&Attribute> = input
706 .attrs
707 .iter()
708 .filter(|a| a.path().is_ident("batpak"))
709 .collect();
710
711 if batpak_attrs.is_empty() {
712 return Err(syn::Error::new(
713 input.ident.span(),
714 "#[derive(MultiEventReactor)] requires `#[batpak(input = <Lane>)]` plus at least one `#[batpak(event = <Payload>, handler = <fn>)]` attribute",
715 ));
716 }
717
718 let mut input_path: Option<Path> = None;
719 let mut error_path: Option<Path> = None;
720 let mut bindings: Vec<EventBinding> = Vec::new();
721 let mut seen_events: HashSet<String> = HashSet::new();
722
723 for attr in &batpak_attrs {
724 match classify_batpak_attr(attr)? {
725 BatpakAttrKind::Config {
726 input: attr_input,
727 cache_version,
728 state_max_cardinality,
729 error: attr_error,
730 } => {
731 if let Some(lit) = cache_version {
732 return Err(syn::Error::new(
733 lit.span(),
734 "`cache_version` is not valid on `#[derive(MultiEventReactor)]` — \
735 `cache_version` is a projection-cache key, not a reactor setting",
736 ));
737 }
738 if let Some(lit) = state_max_cardinality {
739 return Err(syn::Error::new(
740 lit.span(),
741 "`state_max_cardinality` is not valid on `#[derive(MultiEventReactor)]` — \
742 state cardinality is a projection contract, not a reactor setting",
743 ));
744 }
745 if let Some(path) = attr_input {
746 if input_path.is_some() {
747 return Err(syn::Error::new(
748 path.span(),
749 "duplicate `input =` across `#[batpak(...)]` config attributes — `input` must appear exactly once",
750 ));
751 }
752 input_path = Some(path);
753 }
754 if let Some(path) = attr_error {
755 if error_path.is_some() {
756 return Err(syn::Error::new(
757 path.span(),
758 "duplicate `error =` across `#[batpak(...)]` config attributes — `error` must appear exactly once",
759 ));
760 }
761 error_path = Some(path);
762 }
763 }
764 BatpakAttrKind::Event(binding) => {
765 collect_unique_event_binding(&mut bindings, &mut seen_events, binding, "reactor")?;
766 }
767 }
768 }
769
770 let input_path = input_path.ok_or_else(|| {
771 syn::Error::new(
772 input.ident.span(),
773 "#[derive(MultiEventReactor)] requires `#[batpak(input = <Lane>)]` — e.g. `input = JsonValueInput` or `input = RawMsgpackInput`",
774 )
775 })?;
776 let error_path = error_path.ok_or_else(|| {
777 syn::Error::new(
778 input.ident.span(),
779 "#[derive(MultiEventReactor)] requires `#[batpak(error = <ErrorType>)]` — the shared error type all handlers return",
780 )
781 })?;
782
783 if bindings.is_empty() {
784 return Err(syn::Error::new(
785 input.ident.span(),
786 "#[derive(MultiEventReactor)] requires at least one `#[batpak(event = <Payload>, handler = <fn>)]`",
787 ));
788 }
789
790 let ident = &input.ident;
791 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
792
793 let kind_exprs: Vec<proc_macro2::TokenStream> = bindings
794 .iter()
795 .map(|b| {
796 let event_ty = &b.event;
797 quote! {
798 <#event_ty as ::batpak::event::EventPayload>::KIND
799 }
800 })
801 .collect();
802 let kind_count = bindings.len();
803
804 let arms: Vec<proc_macro2::TokenStream> = bindings
810 .iter()
811 .map(|b| {
812 let event_ty = &b.event;
813 let handler_fn = &b.handler;
814 quote! {
815 match ::batpak::event::DecodeTyped::route_typed::<#event_ty>(&event.event) {
816 ::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
817 let __typed_event = ::batpak::event::StoredEvent {
818 coordinate: event.coordinate.clone(),
819 event: ::batpak::event::Event {
820 header: event.event.header.clone(),
821 payload: __p,
822 hash_chain: event.event.hash_chain.clone(),
823 },
824 };
825 return self
826 .#handler_fn(&__typed_event, out, at_least_once)
827 .map_err(::batpak::event::MultiDispatchError::User);
828 }
829 ::core::result::Result::Ok(::core::option::Option::None) => {}
830 ::core::result::Result::Err(__e) => {
831 return ::core::result::Result::Err(
832 ::batpak::event::MultiDispatchError::Decode(__e)
833 );
834 }
835 }
836 }
837 })
838 .collect();
839
840 let handler_checks: Vec<proc_macro2::TokenStream> = bindings
846 .iter()
847 .map(|b| {
848 let event_ty = &b.event;
849 let handler_fn = &b.handler;
850 quote! {
851 let _: fn(
852 &mut Self,
853 &::batpak::event::StoredEvent<#event_ty>,
854 &mut ::batpak::store::ReactionBatch,
855 ::core::option::Option<&::batpak::store::AtLeastOnce>,
856 ) -> ::core::result::Result<(), #error_path> = Self::#handler_fn;
857 }
858 })
859 .collect();
860
861 let attr_assertions = {
866 quote! {
867 const _: fn() = || {
868 fn __batpak_assert_projection_input<T: ::batpak::event::ProjectionInput>() {}
869 __batpak_assert_projection_input::<#input_path>();
870 };
871 const _: fn() = || {
872 fn __batpak_assert_error<
873 T: ::core::marker::Send
874 + ::core::marker::Sync
875 + 'static
876 + ::std::error::Error,
877 >() {}
878 __batpak_assert_error::<#error_path>();
879 };
880 }
881 };
882
883 Ok(quote! {
884 #attr_assertions
885
886 impl #impl_generics ::batpak::event::MultiReactive<#input_path>
887 for #ident #ty_generics #where_clause
888 {
889 type Error = #error_path;
890
891 fn relevant_event_kinds() -> &'static [::batpak::event::EventKind] {
892 static KINDS: [::batpak::event::EventKind; #kind_count] = [
893 #(#kind_exprs),*
894 ];
895 &KINDS
896 }
897
898 fn dispatch(
899 &mut self,
900 event: &::batpak::event::StoredEvent<
901 <#input_path as ::batpak::event::ProjectionInput>::Payload,
902 >,
903 out: &mut ::batpak::store::ReactionBatch,
904 at_least_once: ::core::option::Option<&::batpak::store::AtLeastOnce>,
905 ) -> ::core::result::Result<(), ::batpak::event::MultiDispatchError<Self::Error>> {
906 #(#handler_checks)*
907 #(#arms)*
908 ::core::result::Result::Ok(())
910 }
911 }
912 })
913}