1use proc_macro::TokenStream;
8use quote::{format_ident, quote};
9use std::collections::HashSet;
10use syn::{
11 parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Fields, Ident, LitInt, Path,
12};
13
14#[proc_macro_derive(EventPayload, attributes(batpak))]
19pub fn derive_event_payload(input: TokenStream) -> TokenStream {
20 let input = parse_macro_input!(input as DeriveInput);
21 match expand(&input) {
22 Ok(ts) => ts.into(),
23 Err(e) => e.to_compile_error().into(),
24 }
25}
26
27#[proc_macro_derive(MultiEventReactor, attributes(batpak))]
46pub fn derive_multi_event_reactor(input: TokenStream) -> TokenStream {
47 let input = parse_macro_input!(input as DeriveInput);
48 match expand_multi_event_reactor(&input) {
49 Ok(ts) => ts.into(),
50 Err(e) => e.to_compile_error().into(),
51 }
52}
53
54#[proc_macro_derive(EventSourced, attributes(batpak))]
99pub fn derive_event_sourced(input: TokenStream) -> TokenStream {
100 let input = parse_macro_input!(input as DeriveInput);
101 match expand_event_sourced(&input) {
102 Ok(ts) => ts.into(),
103 Err(e) => e.to_compile_error().into(),
104 }
105}
106
107fn expand(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
108 if !input.generics.params.is_empty() {
109 return Err(syn::Error::new(
110 input.ident.span(),
111 "#[derive(EventPayload)] does not support generic payload types; use a concrete named-field struct",
112 ));
113 }
114
115 let fields = match &input.data {
117 Data::Struct(s) => &s.fields,
118 Data::Enum(e) => {
119 return Err(syn::Error::new(
120 e.enum_token.span,
121 "#[derive(EventPayload)] requires a named-field struct; enums are not supported",
122 ));
123 }
124 Data::Union(u) => {
125 return Err(syn::Error::new(
126 u.union_token.span,
127 "#[derive(EventPayload)] requires a named-field struct; unions are not supported",
128 ));
129 }
130 };
131
132 match fields {
133 Fields::Named(_) => {}
134 Fields::Unnamed(f) => {
135 return Err(syn::Error::new(
136 f.span(),
137 "#[derive(EventPayload)] requires a named-field struct; tuple structs are not supported",
138 ));
139 }
140 Fields::Unit => {
141 return Err(syn::Error::new(
142 input.ident.span(),
143 "#[derive(EventPayload)] requires a named-field struct; unit structs are not supported",
144 ));
145 }
146 }
147
148 let batpak_attrs: Vec<&Attribute> = input
150 .attrs
151 .iter()
152 .filter(|a| a.path().is_ident("batpak"))
153 .collect();
154
155 let attr = match batpak_attrs.as_slice() {
156 [] => {
157 return Err(syn::Error::new(
158 input.ident.span(),
159 "#[derive(EventPayload)] requires a `#[batpak(category = N, type_id = N)]` attribute",
160 ));
161 }
162 [a] => *a,
163 [_, second, ..] => {
164 return Err(syn::Error::new(
165 second.span(),
166 "expected exactly one `#[batpak(...)]` attribute",
167 ));
168 }
169 };
170
171 let mut category_lit: Option<LitInt> = None;
173 let mut type_id_lit: Option<LitInt> = None;
174
175 attr.parse_nested_meta(|meta| {
176 let ident = meta
177 .path
178 .get_ident()
179 .ok_or_else(|| meta.error("expected `category` or `type_id`"))?;
180 match ident.to_string().as_str() {
181 "category" => {
182 if category_lit.is_some() {
183 return Err(meta.error("duplicate `category` key"));
184 }
185 category_lit = Some(meta.value()?.parse::<LitInt>()?);
186 }
187 "type_id" => {
188 if type_id_lit.is_some() {
189 return Err(meta.error("duplicate `type_id` key"));
190 }
191 type_id_lit = Some(meta.value()?.parse::<LitInt>()?);
192 }
193 other => {
194 return Err(meta.error(format!(
195 "unknown key `{other}`, expected `category` or `type_id`"
196 )));
197 }
198 }
199 Ok(())
200 })?;
201
202 let category_lit = category_lit
203 .ok_or_else(|| syn::Error::new(attr.span(), "`#[batpak(...)]` requires `category = N`"))?;
204 let type_id_lit = type_id_lit
205 .ok_or_else(|| syn::Error::new(attr.span(), "`#[batpak(...)]` requires `type_id = N`"))?;
206
207 let category_u64: u64 = category_lit.base10_parse()?;
209 if category_u64 > u64::from(u8::MAX) {
210 return Err(syn::Error::new(
211 category_lit.span(),
212 "category must fit in 4 bits (0x1–0xF, excluding 0x0 and 0xD)",
213 ));
214 }
215 #[allow(clippy::cast_possible_truncation)]
217 let category: u8 = category_u64 as u8;
218 if let Err(msg) = batpak_macros_support::validate_category(category) {
219 return Err(syn::Error::new(category_lit.span(), msg));
220 }
221
222 let type_id_u64: u64 = type_id_lit.base10_parse()?;
223 if type_id_u64 > u64::from(u16::MAX) {
224 return Err(syn::Error::new(
225 type_id_lit.span(),
226 "type_id must fit in 12 bits (0x000–0xFFF)",
227 ));
228 }
229 #[allow(clippy::cast_possible_truncation)]
231 let type_id: u16 = type_id_u64 as u16;
232 if let Err(msg) = batpak_macros_support::validate_type_id(type_id) {
233 return Err(syn::Error::new(type_id_lit.span(), msg));
234 }
235
236 let ident = &input.ident;
238 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
239 let kind_bits: u16 = (u16::from(category) << 12) | type_id;
240 let test_fn_name = format_ident!("__batpak_kind_collision_check_{}", ident);
241
242 Ok(quote! {
248 impl #impl_generics ::batpak::event::EventPayload for #ident #ty_generics #where_clause {
249 const KIND: ::batpak::event::EventKind =
250 ::batpak::event::EventKind::custom(#category, #type_id);
251 }
252
253 const _: () = {
254 ::batpak::__private::inventory::submit! {
255 ::batpak::__private::EventPayloadRegistration {
256 kind_bits: #kind_bits,
257 type_name: concat!(module_path!(), "::", stringify!(#ident)),
258 }
259 }
260 };
261
262 #[cfg(test)]
263 #[test]
264 #[allow(non_snake_case)]
266 fn #test_fn_name() {
267 ::batpak::__private::assert_no_kind_collisions();
268 }
269 })
270}
271
272struct EventBinding {
277 event: Path,
278 handler: Ident,
279}
280
281enum BatpakAttrKind {
287 Config {
288 input: Option<Path>,
289 cache_version: Option<LitInt>,
290 error: Option<Path>,
291 },
292 Event(EventBinding),
293}
294
295fn classify_batpak_attr(attr: &Attribute) -> syn::Result<BatpakAttrKind> {
296 let mut input: Option<Path> = None;
298 let mut cache_version: Option<LitInt> = None;
299 let mut error_ty: Option<Path> = None;
300 let mut event: Option<Path> = None;
301 let mut handler: Option<Ident> = None;
302
303 attr.parse_nested_meta(|meta| {
304 let key = meta.path.get_ident().ok_or_else(|| {
305 meta.error("expected `input`, `cache_version`, `error`, `event`, or `handler`")
306 })?;
307 match key.to_string().as_str() {
308 "input" => {
309 if input.is_some() {
310 return Err(meta.error("duplicate `input` key within attribute"));
311 }
312 input = Some(meta.value()?.parse::<Path>()?);
313 }
314 "cache_version" => {
315 if cache_version.is_some() {
316 return Err(meta.error("duplicate `cache_version` key within attribute"));
317 }
318 cache_version = Some(meta.value()?.parse::<LitInt>()?);
319 }
320 "error" => {
321 if error_ty.is_some() {
322 return Err(meta.error("duplicate `error` key within attribute"));
323 }
324 error_ty = Some(meta.value()?.parse::<Path>()?);
325 }
326 "event" => {
327 if event.is_some() {
328 return Err(meta.error("duplicate `event` key within attribute"));
329 }
330 event = Some(meta.value()?.parse::<Path>()?);
331 }
332 "handler" => {
333 if handler.is_some() {
334 return Err(meta.error("duplicate `handler` key within attribute"));
335 }
336 handler = Some(meta.value()?.parse::<Ident>()?);
337 }
338 other => {
339 return Err(meta.error(format!(
340 "unknown key `{other}`, expected `input`, `cache_version`, `error`, `event`, or `handler`"
341 )));
342 }
343 }
344 Ok(())
345 })?;
346
347 let has_config = input.is_some() || cache_version.is_some() || error_ty.is_some();
348 let has_event = event.is_some() || handler.is_some();
349
350 if has_config && has_event {
351 return Err(syn::Error::new(
352 attr.span(),
353 "`#[batpak(...)]` attribute must contain either config keys \
354 (`input`, `cache_version`, `error`) or an event-binding pair (`event`, `handler`), not both",
355 ));
356 }
357
358 if has_event {
359 let event = event.ok_or_else(|| {
360 syn::Error::new(
361 attr.span(),
362 "event-binding attribute is missing `event = <PayloadType>`",
363 )
364 })?;
365 let handler = handler.ok_or_else(|| {
366 syn::Error::new(
367 attr.span(),
368 "event-binding attribute is missing `handler = <fn_name>`",
369 )
370 })?;
371 return Ok(BatpakAttrKind::Event(EventBinding { event, handler }));
372 }
373
374 if !has_config {
376 return Err(syn::Error::new(
377 attr.span(),
378 "`#[batpak(...)]` must contain at least one key: `input`, `cache_version`, `error`, or the `event`/`handler` pair",
379 ));
380 }
381 Ok(BatpakAttrKind::Config {
382 input,
383 cache_version,
384 error: error_ty,
385 })
386}
387
388fn expand_event_sourced(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
389 match &input.data {
391 Data::Struct(s) => match &s.fields {
392 Fields::Named(_) => {}
393 Fields::Unnamed(f) => {
394 return Err(syn::Error::new(
395 f.span(),
396 "#[derive(EventSourced)] requires a named-field struct; tuple structs are not supported",
397 ));
398 }
399 Fields::Unit => {
400 return Err(syn::Error::new(
401 input.ident.span(),
402 "#[derive(EventSourced)] requires a named-field struct; unit structs are not supported",
403 ));
404 }
405 },
406 Data::Enum(e) => {
407 return Err(syn::Error::new(
408 e.enum_token.span,
409 "#[derive(EventSourced)] requires a named-field struct; enums are not supported",
410 ));
411 }
412 Data::Union(u) => {
413 return Err(syn::Error::new(
414 u.union_token.span,
415 "#[derive(EventSourced)] requires a named-field struct; unions are not supported",
416 ));
417 }
418 }
419
420 let batpak_attrs: Vec<&Attribute> = input
422 .attrs
423 .iter()
424 .filter(|a| a.path().is_ident("batpak"))
425 .collect();
426
427 if batpak_attrs.is_empty() {
428 return Err(syn::Error::new(
429 input.ident.span(),
430 "#[derive(EventSourced)] requires at least one `#[batpak(input = <Lane>)]` attribute",
431 ));
432 }
433
434 let mut input_path: Option<Path> = None;
435 let mut cache_version_lit: Option<LitInt> = None;
436 let mut bindings: Vec<EventBinding> = Vec::new();
437 let mut seen_events: HashSet<String> = HashSet::new();
438
439 for attr in &batpak_attrs {
440 match classify_batpak_attr(attr)? {
441 BatpakAttrKind::Config {
442 input: attr_input,
443 cache_version: attr_cache,
444 error: attr_error,
445 } => {
446 if let Some(path) = attr_error {
447 return Err(syn::Error::new(
448 path.span(),
449 "`error` is not valid on `#[derive(EventSourced)]` — projections do not have an associated error type",
450 ));
451 }
452 if let Some(path) = attr_input {
453 if input_path.is_some() {
454 return Err(syn::Error::new(
455 path.span(),
456 "duplicate `input =` across `#[batpak(...)]` config attributes — `input` must appear exactly once",
457 ));
458 }
459 input_path = Some(path);
460 }
461 if let Some(lit) = attr_cache {
462 if cache_version_lit.is_some() {
463 return Err(syn::Error::new(
464 lit.span(),
465 "duplicate `cache_version =` across `#[batpak(...)]` config attributes",
466 ));
467 }
468 cache_version_lit = Some(lit);
469 }
470 }
471 BatpakAttrKind::Event(binding) => {
472 require_single_segment_event_path(&binding.event)?;
473 let key = binding.event.to_token_stream_string();
474 if !seen_events.insert(key) {
475 return Err(syn::Error::new(
476 binding.event.span(),
477 "duplicate `event = X` — each payload type may be bound to exactly one handler per projection",
478 ));
479 }
480 bindings.push(binding);
481 }
482 }
483 }
484
485 let input_path = input_path.ok_or_else(|| {
486 syn::Error::new(
487 input.ident.span(),
488 "#[derive(EventSourced)] requires `#[batpak(input = <Lane>)]` — e.g. `input = JsonValueInput` or `input = RawMsgpackInput`",
489 )
490 })?;
491
492 if bindings.is_empty() {
493 return Err(syn::Error::new(
494 input.ident.span(),
495 "`#[derive(EventSourced)]` requires at least one `#[batpak(event = T, handler = h)]` binding",
496 ));
497 }
498
499 let cache_version_value: u64 = match &cache_version_lit {
501 Some(lit) => lit.base10_parse::<u64>()?,
502 None => 0u64,
503 };
504
505 let ident = &input.ident;
507 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
508
509 let arms: Vec<proc_macro2::TokenStream> = bindings
514 .iter()
515 .map(|b| {
516 let event_ty = &b.event;
517 let handler_fn = &b.handler;
518 quote! {
519 match ::batpak::event::DecodeTyped::route_typed::<#event_ty>(event) {
524 ::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
525 self.#handler_fn(&__p);
526 return;
527 }
528 ::core::result::Result::Ok(::core::option::Option::None) => {}
529 ::core::result::Result::Err(__e) => {
530 ::core::panic!(
531 "EventSourced: decode failed for matched kind {}: {}",
532 ::core::stringify!(#event_ty),
533 __e
534 );
535 }
536 }
537 }
538 })
539 .collect();
540
541 let kind_exprs: Vec<proc_macro2::TokenStream> = bindings
543 .iter()
544 .map(|b| {
545 let event_ty = &b.event;
546 quote! {
547 <#event_ty as ::batpak::event::EventPayload>::KIND
548 }
549 })
550 .collect();
551 let kind_count = bindings.len();
552
553 let handler_checks: Vec<proc_macro2::TokenStream> = bindings
560 .iter()
561 .map(|b| {
562 let event_ty = &b.event;
563 let handler_fn = &b.handler;
564 quote! {
565 let _: fn(&mut Self, &#event_ty) = Self::#handler_fn;
566 }
567 })
568 .collect();
569
570 let input_assertion = {
575 quote! {
576 const _: fn() = || {
577 fn __batpak_assert_projection_input<T: ::batpak::event::ProjectionInput>() {}
578 __batpak_assert_projection_input::<#input_path>();
579 };
580 }
581 };
582
583 Ok(quote! {
584 #input_assertion
585
586 impl #impl_generics ::batpak::event::EventSourced for #ident #ty_generics #where_clause {
587 type Input = #input_path;
588
589 fn from_events(
590 events: &[::batpak::event::ProjectionEvent<Self>],
591 ) -> ::core::option::Option<Self> {
592 if events.is_empty() {
593 return ::core::option::Option::None;
594 }
595 let mut state: Self = ::core::default::Default::default();
596 for __ev in events {
597 state.apply_event(__ev);
598 }
599 ::core::option::Option::Some(state)
600 }
601
602 fn apply_event(&mut self, event: &::batpak::event::ProjectionEvent<Self>) {
603 #(#handler_checks)*
604 #(#arms)*
609 let _ = event;
611 }
612
613 fn relevant_event_kinds() -> &'static [::batpak::event::EventKind] {
614 static KINDS: [::batpak::event::EventKind; #kind_count] = [
615 #(#kind_exprs),*
616 ];
617 &KINDS
618 }
619
620 fn schema_version() -> u64 {
621 #cache_version_value
625 }
626 }
627 })
628}
629
630trait ToTokenStreamString {
631 fn to_token_stream_string(&self) -> String;
632}
633
634impl ToTokenStreamString for Path {
635 fn to_token_stream_string(&self) -> String {
636 quote!(#self).to_string()
637 }
638}
639
640fn require_single_segment_event_path(path: &Path) -> syn::Result<()> {
650 if path.leading_colon.is_some() || path.segments.len() != 1 {
651 return Err(syn::Error::new_spanned(
652 path,
653 "event type must be named by its in-scope single-segment name — use a `use` import if the type is in another module",
654 ));
655 }
656 Ok(())
657}
658
659fn expand_multi_event_reactor(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
662 match &input.data {
664 Data::Struct(s) => match &s.fields {
665 Fields::Named(_) => {}
666 Fields::Unnamed(f) => {
667 return Err(syn::Error::new(
668 f.span(),
669 "#[derive(MultiEventReactor)] requires a named-field struct; tuple structs are not supported",
670 ));
671 }
672 Fields::Unit => {
673 return Err(syn::Error::new(
674 input.ident.span(),
675 "#[derive(MultiEventReactor)] requires a named-field struct; unit structs are not supported",
676 ));
677 }
678 },
679 Data::Enum(e) => {
680 return Err(syn::Error::new(
681 e.enum_token.span,
682 "#[derive(MultiEventReactor)] requires a named-field struct; enums are not supported",
683 ));
684 }
685 Data::Union(u) => {
686 return Err(syn::Error::new(
687 u.union_token.span,
688 "#[derive(MultiEventReactor)] requires a named-field struct; unions are not supported",
689 ));
690 }
691 }
692
693 let batpak_attrs: Vec<&Attribute> = input
694 .attrs
695 .iter()
696 .filter(|a| a.path().is_ident("batpak"))
697 .collect();
698
699 if batpak_attrs.is_empty() {
700 return Err(syn::Error::new(
701 input.ident.span(),
702 "#[derive(MultiEventReactor)] requires `#[batpak(input = <Lane>)]` plus at least one `#[batpak(event = <Payload>, handler = <fn>)]` attribute",
703 ));
704 }
705
706 let mut input_path: Option<Path> = None;
707 let mut error_path: Option<Path> = None;
708 let mut bindings: Vec<EventBinding> = Vec::new();
709 let mut seen_events: HashSet<String> = HashSet::new();
710
711 for attr in &batpak_attrs {
712 match classify_batpak_attr(attr)? {
713 BatpakAttrKind::Config {
714 input: attr_input,
715 cache_version,
716 error: attr_error,
717 } => {
718 if let Some(lit) = cache_version {
719 return Err(syn::Error::new(
720 lit.span(),
721 "`cache_version` is not valid on `#[derive(MultiEventReactor)]` — \
722 `cache_version` is a projection-cache key, not a reactor setting",
723 ));
724 }
725 if let Some(path) = attr_input {
726 if input_path.is_some() {
727 return Err(syn::Error::new(
728 path.span(),
729 "duplicate `input =` across `#[batpak(...)]` config attributes — `input` must appear exactly once",
730 ));
731 }
732 input_path = Some(path);
733 }
734 if let Some(path) = attr_error {
735 if error_path.is_some() {
736 return Err(syn::Error::new(
737 path.span(),
738 "duplicate `error =` across `#[batpak(...)]` config attributes — `error` must appear exactly once",
739 ));
740 }
741 error_path = Some(path);
742 }
743 }
744 BatpakAttrKind::Event(binding) => {
745 require_single_segment_event_path(&binding.event)?;
746 let key = binding.event.to_token_stream_string();
747 if !seen_events.insert(key) {
748 return Err(syn::Error::new(
749 binding.event.span(),
750 "duplicate `event = X` — each payload type may be bound to exactly one handler per reactor",
751 ));
752 }
753 bindings.push(binding);
754 }
755 }
756 }
757
758 let input_path = input_path.ok_or_else(|| {
759 syn::Error::new(
760 input.ident.span(),
761 "#[derive(MultiEventReactor)] requires `#[batpak(input = <Lane>)]` — e.g. `input = JsonValueInput` or `input = RawMsgpackInput`",
762 )
763 })?;
764 let error_path = error_path.ok_or_else(|| {
765 syn::Error::new(
766 input.ident.span(),
767 "#[derive(MultiEventReactor)] requires `#[batpak(error = <ErrorType>)]` — the shared error type all handlers return",
768 )
769 })?;
770
771 if bindings.is_empty() {
772 return Err(syn::Error::new(
773 input.ident.span(),
774 "#[derive(MultiEventReactor)] requires at least one `#[batpak(event = <Payload>, handler = <fn>)]`",
775 ));
776 }
777
778 let ident = &input.ident;
779 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
780
781 let kind_exprs: Vec<proc_macro2::TokenStream> = bindings
782 .iter()
783 .map(|b| {
784 let event_ty = &b.event;
785 quote! {
786 <#event_ty as ::batpak::event::EventPayload>::KIND
787 }
788 })
789 .collect();
790 let kind_count = bindings.len();
791
792 let arms: Vec<proc_macro2::TokenStream> = bindings
798 .iter()
799 .map(|b| {
800 let event_ty = &b.event;
801 let handler_fn = &b.handler;
802 quote! {
803 match ::batpak::event::DecodeTyped::route_typed::<#event_ty>(&event.event) {
804 ::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
805 let __typed_event = ::batpak::event::StoredEvent {
806 coordinate: event.coordinate.clone(),
807 event: ::batpak::event::Event {
808 header: event.event.header.clone(),
809 payload: __p,
810 hash_chain: event.event.hash_chain.clone(),
811 },
812 };
813 return self
814 .#handler_fn(&__typed_event, out, at_least_once)
815 .map_err(::batpak::event::MultiDispatchError::User);
816 }
817 ::core::result::Result::Ok(::core::option::Option::None) => {}
818 ::core::result::Result::Err(__e) => {
819 return ::core::result::Result::Err(
820 ::batpak::event::MultiDispatchError::Decode(__e)
821 );
822 }
823 }
824 }
825 })
826 .collect();
827
828 let handler_checks: Vec<proc_macro2::TokenStream> = bindings
834 .iter()
835 .map(|b| {
836 let event_ty = &b.event;
837 let handler_fn = &b.handler;
838 quote! {
839 let _: fn(
840 &mut Self,
841 &::batpak::event::StoredEvent<#event_ty>,
842 &mut ::batpak::store::ReactionBatch,
843 ::core::option::Option<&::batpak::store::AtLeastOnce>,
844 ) -> ::core::result::Result<(), #error_path> = Self::#handler_fn;
845 }
846 })
847 .collect();
848
849 let attr_assertions = {
854 quote! {
855 const _: fn() = || {
856 fn __batpak_assert_projection_input<T: ::batpak::event::ProjectionInput>() {}
857 __batpak_assert_projection_input::<#input_path>();
858 };
859 const _: fn() = || {
860 fn __batpak_assert_error<
861 T: ::core::marker::Send
862 + ::core::marker::Sync
863 + 'static
864 + ::std::error::Error,
865 >() {}
866 __batpak_assert_error::<#error_path>();
867 };
868 }
869 };
870
871 Ok(quote! {
872 #attr_assertions
873
874 impl #impl_generics ::batpak::event::MultiReactive<#input_path>
875 for #ident #ty_generics #where_clause
876 {
877 type Error = #error_path;
878
879 fn relevant_event_kinds() -> &'static [::batpak::event::EventKind] {
880 static KINDS: [::batpak::event::EventKind; #kind_count] = [
881 #(#kind_exprs),*
882 ];
883 &KINDS
884 }
885
886 fn dispatch(
887 &mut self,
888 event: &::batpak::event::StoredEvent<
889 <#input_path as ::batpak::event::ProjectionInput>::Payload,
890 >,
891 out: &mut ::batpak::store::ReactionBatch,
892 at_least_once: ::core::option::Option<&::batpak::store::AtLeastOnce>,
893 ) -> ::core::result::Result<(), ::batpak::event::MultiDispatchError<Self::Error>> {
894 #(#handler_checks)*
895 #(#arms)*
896 ::core::result::Result::Ok(())
898 }
899 }
900 })
901}