pub use crate::parse::EventsEnum;
use crate::parse::deps::*;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
type Result = syn::Result<TokenStream>;
pub fn event_handler_codegen(events_enum: &EventsEnum) -> Result {
let event_handler = gen_event_handler(events_enum)?;
crate::parse::embed_generated_code(
events_enum.embed_generated_code,
&events_enum.name,
event_handler,
"event_handler",
)
}
fn gen_event_handler(events_enum: &EventsEnum) -> Result {
let cidomap = &events_enum.cidomap;
let enum_name = &events_enum.name;
let event_parse_trigger_body = gen_parse_trigger_body(events_enum)?;
let event_handler_body = gen_event_handler_fn_body(events_enum);
let event_preprocessor_body = gen_event_preprocessor_fn_body(events_enum)?;
let event_source_body = gen_event_source_body(events_enum)?;
let event_error_ty = quote! { <#cidomap as ::cido::__internal::Cidomap>::Error };
let default_handler: syn::Type = syn::parse_quote!(CidoParsingErrorHandler);
let (handler_type, handler_body) = match &events_enum.error_handler {
crate::parse::ErrorFlow::Break => (
default_handler,
Some(quote! {
async move { ::core::ops::ControlFlow::Break(error) }.boxed()
}),
),
crate::parse::ErrorFlow::Continue => (
default_handler,
Some(quote! {
async move { ::core::ops::ControlFlow::Continue(error) }.boxed()
}),
),
crate::parse::ErrorFlow::HandlerFn(func) => (
default_handler,
Some(quote! {
async move { #func(ctx, error).await }.boxed()
}),
),
crate::parse::ErrorFlow::HandlerTrait(name) => (name.clone(), None),
};
let handler_impl = if let Some(handler_body) = handler_body {
quote! {
#[derive(Default)]
pub struct CidoParsingErrorHandler;
impl ::cido::__internal::ParsingContext<#enum_name> for CidoParsingErrorHandler {
#[allow(unused)]
fn handle_error<'a>(
&'a mut self,
ctx: impl ::cido::__internal::ParseContext<
'a,
<#enum_name as ::cido::__internal::EventHandler>::Cidomap
>,
error: #event_error_ty,
) -> ::cido::__internal::futures::future::BoxFuture<'a, ::core::ops::ControlFlow<
#event_error_ty,
#event_error_ty
>>
{
use ::cido::__internal::futures::future::FutureExt;
#handler_body
}
}
}
} else {
quote! {}
};
let (cacheless_name, enum_def) = gen_event_enum(events_enum)?;
let event_handler_impl = quote! {
#enum_def
#handler_impl
impl ::cido::__internal::EventHandler for #enum_name {
type Cidomap = #cidomap;
type Cacheless = #cacheless_name;
type ContextHandler = #handler_type;
fn parse_trigger(
handler: &mut Self::ContextHandler,
trigger: &<<Self::Cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::Trigger,
filter: &<<Self::Cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::TriggerFilter,
) -> ::core::result::Result<Self::Cacheless, #event_error_ty> {
#event_parse_trigger_body
}
async fn event_handler<'a>(
self,
ctx: ::cido::__internal::Context<'a, Self::Cidomap>,
meta: ::cido::__internal::MetaEvent<Self::Cidomap>,
) ->
::core::result::Result<(), #event_error_ty>
{
#event_handler_body
}
async fn event_source<'a>(
&self,
ctx: ::cido::__internal::GeneratorContext<'a, Self::Cidomap>,
meta: &'a ::cido::__internal::MetaEvent<Self::Cidomap>,
) ->
::core::result::Result<(), #event_error_ty>
{
#event_source_body
}
fn event_preprocessor<'a>(
ctx: &'a ::cido::__internal::PreprocessingContext<Self::Cidomap>,
meta: ::cido::__internal::MetaEvent<Self::Cidomap>,
event: Self::Cacheless,
) -> ::cido::__internal::either::Either<
::cido::__internal::futures::future::BoxFuture<'a, ::core::result::Result<(cido::__internal::MetaEvent<Self::Cidomap>, Self), #event_error_ty>>,
::core::result::Result<(cido::__internal::MetaEvent<Self::Cidomap>, Self), #event_error_ty>
>
{
#event_preprocessor_body
}
}
};
Ok(event_handler_impl)
}
fn gen_event_handler_fn_body(events_enum: &EventsEnum) -> TokenStream {
let event_handlers = events_enum.events.iter().map(|def| {
let callable = &def.config.func;
let variant = &def.name;
let variant_name = variant.to_string();
quote! {
Self::#variant(ev, cache) => {
Handler::<_, _, ::std::result::Result<(), <Self::Cidomap as ::cido::prelude::Cidomap>::Error>>::call(#callable, (ctx, meta, Event(ev), Cache(cache)))
.meter_ms(#variant_name, 100)
.await
.map_err(|e| {
::cido::__internal::tracing::error!("Error while running {}: {}", #variant_name, e);
e
})?;
}
}
});
quote! {
use ::cido::__internal::tracing::Instrument;
use ::cido::__internal::FutureMeter;
use ::cido::__internal::{Cache, Event, Handler};
use ::core::convert::TryInto;
let span = ::cido::__internal::tracing::debug_span!("event_matcher").or_current();
async {
match self {
#(#event_handlers),*
}
::core::result::Result::Ok(())
}
.instrument(span)
.await
}
}
fn gen_event_preprocessor_fn_body(events_enum: &EventsEnum) -> syn::Result<TokenStream> {
let variants = events_enum.events.iter().map(|ev_def| {
let ev_variant = &ev_def.name;
let ev_type = &ev_def.ty;
match ev_def.config.preprocessor.as_ref() {
Some(p) => {
let cache_type = &p.cache;
let preprocessor_fn = &p.func;
quote! {
Self::Cacheless::#ev_variant(ev) => {
let boxed_meta_event = ::std::boxed::Box::new((meta, ev));
let ev_ref = unsafe {&* (&boxed_meta_event.1 as *const #ev_type)};
let meta_ref = unsafe {&* (&boxed_meta_event.0 as *const ::cido::prelude::MetaEvent<Self::Cidomap>)};
let f = Handler::<_, _, Result<#cache_type, <Self::Cidomap as ::cido::prelude::Cidomap>::Error>>::call(
#preprocessor_fn, (ctx, meta_ref, Event(ev_ref), ())
);
::cido::__internal::either::Either::Left(::std::boxed::Box::pin(async move {
let cache = f.await?;
let (meta, ev) = *boxed_meta_event;
::core::result::Result::Ok((meta, Self::#ev_variant(ev, cache)))
}))
}
}
}
None => {
quote! {
Self::Cacheless::#ev_variant(ev) => {
::cido::__internal::either::Either::Right(::core::result::Result::Ok((meta, Self::#ev_variant(ev, ()))))
}
}
}
}
});
let fn_body = quote! {
use ::core::convert::Into;
use ::cido::__internal::{Cache, Event, Handler};
match event {
#(#variants)*
}
};
Ok(fn_body)
}
fn gen_event_source_body(events_enum: &EventsEnum) -> Result {
let generators = events_enum
.events
.iter()
.filter_map(|e| e.config.generator.as_ref().map(|g| (e, g)));
let mut variants = vec![];
for (ev_def, source_gen) in generators {
let ev_variant = &ev_def.name;
let match_stmt = quote! {
Self::#ev_variant(ev, cache) => {
Handler::<_, _, Result<(), <Self::Cidomap as ::cido::prelude::Cidomap>::Error>>::call(#source_gen, (ctx, meta, Event(ev), Cache(cache))).await
},
};
variants.push(match_stmt);
}
Ok(quote! {
use ::core::convert::TryInto;
use ::cido::__internal::futures::FutureExt;
use ::cido::__internal::{Cache, Event, Handler};
::cido::__internal::tracing::trace!("Running event spawner for {}", meta.event_order());
match self {
#(#variants)*
_ => {
::core::result::Result::Ok(())
}
}
})
}
fn gen_parse_trigger_body(events_enum: &EventsEnum) -> Result {
let try_from_triggers = events_enum.events.iter().map(|e| {
let ev_variant = &e.name;
let ty = &e.ty;
quote! {
if let ::core::option::Option::Some(v) = #ty::try_from_trigger(trigger, filter) {
return v.map(Self::Cacheless::#ev_variant)
.map_err(|err| ::std::boxed::Box::new(err) as ::std::boxed::Box<dyn ::std::error::Error + ::core::marker::Send + ::core::marker::Sync>)
.map_err(::core::convert::Into::into);
}
}
});
Ok(quote! {
use ::cido::__internal::TryFromTrigger;
use ::core::convert::Into;
#(#try_from_triggers)*
let error: ::std::boxed::Box<dyn ::std::error::Error + ::core::marker::Send + ::core::marker::Sync> = ::std::format!("Unable to find trigger parser for {:?}", trigger).into();
::core::result::Result::Err(error.into())
})
}
pub fn gen_event_enum(events_enum: &EventsEnum) -> syn::Result<(Ident, TokenStream)> {
let event_enum_name = &events_enum.name;
let event_enum_name_cacheless = format_ident!("{event_enum_name}Cacheless");
let variants_cacheless = events_enum.events.iter().map(|e| {
let name = &e.name;
let ty = &e.ty;
quote! {#name(#ty),}
});
let variants = events_enum.events.iter().map(|e| {
let name = &e.name;
let ty = &e.ty;
let cache = e
.config
.preprocessor
.as_ref()
.map(|p| {
let cache = &p.cache;
quote! {#cache}
})
.unwrap_or_else(|| quote! {()});
quote! {#name(#ty, #cache),}
});
let must_use_cacheless = format!("{event_enum_name_cacheless} must be used");
let must_use = format!("{event_enum_name} must be used");
let defs = quote! {
#[must_use = #must_use_cacheless]
#[derive(Debug)]
pub enum #event_enum_name_cacheless {
#(#variants_cacheless)*
}
#[derive(Debug)]
#[must_use = #must_use]
pub enum #event_enum_name {
#(#variants)*
}
};
Ok((event_enum_name_cacheless, defs))
}