#[cfg(test)]
const FIXED_LOCATION: &Location = Location::caller();
use crate::{description, prelude::DependencyMap, HandlerDescription};
use std::{
any::TypeId,
collections::{BTreeMap, BTreeSet},
fmt::Write,
future::Future,
hash::{Hash, Hasher},
ops::ControlFlow,
panic::Location,
sync::Arc,
};
use colored::Colorize;
use futures::future::BoxFuture;
pub struct Handler<'a, Output, Descr = description::Unspecified> {
data: Arc<HandlerData<Descr, DynFn<'a, Output>>>,
}
struct HandlerData<Descr, F: ?Sized> {
description: Descr,
sig: HandlerSignature,
f: F,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HandlerSignature {
Entry,
Other {
obligations: BTreeMap<Type, &'static Location<'static>>,
guaranteed_outcomes: BTreeSet<Type>,
conditional_outcomes: BTreeSet<Type>,
continues: bool,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Type {
pub name: &'static str,
pub id: TypeId,
}
impl Hash for Type {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
type DynFn<'a, Output> =
dyn Fn(DependencyMap, Cont<'a, Output>) -> HandlerResult<'a, Output> + Send + Sync + 'a;
pub type Cont<'a, Output> =
Box<dyn FnOnce(DependencyMap) -> HandlerResult<'a, Output> + Send + Sync + 'a>;
pub type HandlerResult<'a, Output> = BoxFuture<'a, ControlFlow<Output, DependencyMap>>;
impl<Output, Descr> Clone for Handler<'_, Output, Descr> {
fn clone(&self) -> Self {
Handler { data: Arc::clone(&self.data) }
}
}
impl<'a, Output, Descr> Handler<'a, Output, Descr>
where
Output: 'a,
Descr: HandlerDescription,
{
#[must_use]
#[track_caller]
pub fn chain(self, next: Self) -> Self {
let required_update_kinds_set = self.description().merge_chain(next.description());
let new_sig = match (&self.data.sig, &next.data.sig) {
(_, HandlerSignature::Entry) => {
panic!("Ill-typed handler chain: the second handler cannot be an entry")
}
(HandlerSignature::Entry, other_sig @ HandlerSignature::Other { .. }) => {
other_sig.clone()
}
(
HandlerSignature::Other {
obligations: self_obligations,
guaranteed_outcomes: self_guaranteed_outcomes,
conditional_outcomes: self_conditional_outcomes,
continues: self_continues,
},
HandlerSignature::Other {
obligations: next_obligations,
guaranteed_outcomes: next_guaranteed_outcomes,
conditional_outcomes: next_conditional_outcomes,
continues: next_continues,
},
) => {
if !self_continues {
panic!(
"Dead code detected: since the first handler aborts execution, the second \
handler will never be called."
);
}
Self::infer_chain(
self_obligations,
self_guaranteed_outcomes,
self_conditional_outcomes,
next_obligations,
next_guaranteed_outcomes,
next_conditional_outcomes,
*next_continues,
)
}
};
from_fn_with_description(
required_update_kinds_set,
move |event, cont| {
let this = self.clone();
let next = next.clone();
this.execute(event, |event| next.execute(event, cont))
},
new_sig,
)
}
fn infer_chain(
self_obligations: &BTreeMap<Type, &'static Location<'static>>,
self_guaranteed_outcomes: &BTreeSet<Type>,
self_conditional_outcomes: &BTreeSet<Type>,
next_obligations: &BTreeMap<Type, &'static Location<'static>>,
next_guaranteed_outcomes: &BTreeSet<Type>,
next_conditional_outcomes: &BTreeSet<Type>,
next_continues: bool,
) -> HandlerSignature {
HandlerSignature::Other {
obligations: next_obligations
.clone()
.into_iter()
.filter(|(ty, _location)| {
!self_guaranteed_outcomes.contains(ty)
&& !self_conditional_outcomes.contains(ty)
})
.chain(self_obligations.clone())
.collect(),
guaranteed_outcomes: self_guaranteed_outcomes
.union(next_guaranteed_outcomes)
.cloned()
.collect(),
conditional_outcomes: self_conditional_outcomes
.union(next_conditional_outcomes)
.cloned()
.collect(),
continues: next_continues,
}
}
#[must_use]
#[track_caller]
pub fn branch(self, next: Self) -> Self
where
Output: Send,
{
let required_update_kinds_set = self.description().merge_branch(next.description());
let new_sig = match (&self.data.sig, &next.data.sig) {
(_, HandlerSignature::Entry) => {
panic!("Ill-typed handler branch: the second handler cannot be an entry")
}
(HandlerSignature::Entry, other_sig @ HandlerSignature::Other { .. }) => {
other_sig.clone()
}
(
HandlerSignature::Other {
obligations: self_obligations,
guaranteed_outcomes: self_guaranteed_outcomes,
conditional_outcomes: self_conditional_outcomes,
continues: _self_continues,
},
HandlerSignature::Other {
obligations: next_obligations,
guaranteed_outcomes: _next_guaranteed_outcomes,
conditional_outcomes: next_conditional_outcomes,
continues: _next_continues,
},
) => Self::infer_branch(
self_obligations,
self_guaranteed_outcomes,
self_conditional_outcomes,
next_obligations,
next_conditional_outcomes,
),
};
from_fn_with_description(
required_update_kinds_set,
move |event, cont| {
let this = self.clone();
let next = next.clone();
this.execute(event, |event| async move {
match next.dispatch(event).await {
ControlFlow::Continue(event) => cont(event).await,
done => done,
}
})
},
new_sig,
)
}
fn infer_branch(
self_obligations: &BTreeMap<Type, &'static Location<'static>>,
self_guaranteed_outcomes: &BTreeSet<Type>,
self_conditional_outcomes: &BTreeSet<Type>,
next_obligations: &BTreeMap<Type, &'static Location<'static>>,
next_conditional_outcomes: &BTreeSet<Type>,
) -> HandlerSignature {
HandlerSignature::Other {
obligations: next_obligations
.clone()
.into_iter()
.filter(|(ty, _location)| !self_guaranteed_outcomes.contains(ty))
.chain(self_obligations.clone())
.collect(),
guaranteed_outcomes: self_guaranteed_outcomes.clone(),
conditional_outcomes: self_conditional_outcomes
.union(next_conditional_outcomes)
.cloned()
.collect(),
continues: true,
}
}
pub async fn execute<Cont, ContFut>(
self,
input: DependencyMap,
cont: Cont,
) -> ControlFlow<Output, DependencyMap>
where
Cont: FnOnce(DependencyMap) -> ContFut,
Cont: Send + Sync + 'a,
ContFut: Future<Output = ControlFlow<Output, DependencyMap>> + Send + 'a,
{
(self.data.f)(input, Box::new(|event| Box::pin(cont(event)))).await
}
pub async fn dispatch(&self, input: DependencyMap) -> ControlFlow<Output, DependencyMap> {
self.clone().execute(input, |event| async move { ControlFlow::Continue(event) }).await
}
pub fn description(&self) -> &Descr {
&self.data.description
}
pub fn sig(&self) -> &HandlerSignature {
&self.data.sig
}
}
fn print_types<'a>(
types: impl IntoIterator<Item = &'a Type>,
f: impl Fn(&Type) -> String,
) -> String {
let mut res = String::new();
let mut types = types.into_iter();
if let Some(ty) = types.next() {
write!(res, "{}", f(ty)).unwrap();
}
for ty in types {
write!(res, "\n {}", f(ty)).unwrap();
}
res
}
impl Type {
#[must_use]
pub fn of<T>() -> Self
where
T: 'static,
{
Self { id: TypeId::of::<T>(), name: std::any::type_name::<T>() }
}
}
#[must_use]
pub fn from_fn<'a, F, Fut, Output, Descr>(f: F, sig: HandlerSignature) -> Handler<'a, Output, Descr>
where
F: Fn(DependencyMap, Cont<'a, Output>) -> Fut,
F: Send + Sync + 'a,
Fut: Future<Output = ControlFlow<Output, DependencyMap>> + Send + 'a,
Descr: HandlerDescription,
{
from_fn_with_description(Descr::user_defined(), f, sig)
}
#[must_use]
pub fn from_fn_with_description<'a, F, Fut, Output, Descr>(
description: Descr,
f: F,
sig: HandlerSignature,
) -> Handler<'a, Output, Descr>
where
F: Fn(DependencyMap, Cont<'a, Output>) -> Fut,
F: Send + Sync + 'a,
Fut: Future<Output = ControlFlow<Output, DependencyMap>> + Send + 'a,
{
Handler {
data: Arc::new(HandlerData {
f: move |event, cont| Box::pin(f(event, cont)) as HandlerResult<_>,
description,
sig,
}),
}
}
#[must_use]
#[track_caller]
pub fn entry<'a, Output, Descr>() -> Handler<'a, Output, Descr>
where
Output: 'a,
Descr: HandlerDescription,
{
from_fn_with_description(Descr::entry(), |event, cont| cont(event), HandlerSignature::Entry)
}
pub fn type_check(sig: &HandlerSignature, container: &DependencyMap, assumptions: &[Type]) {
match sig {
HandlerSignature::Entry => {}
HandlerSignature::Other {
obligations,
guaranteed_outcomes: _,
conditional_outcomes: _,
continues: _,
} => {
let container_types = container
.map
.iter()
.map(|(type_id, dep)| Type { id: *type_id, name: dep.type_name })
.chain(assumptions.iter().cloned())
.collect::<BTreeSet<_>>();
let handler_accepts_msg = "Your handler accepts the following types:".to_owned();
let provided_types_msg = "But only the following values were given to it:".to_owned();
let missing_types_msg = "The missing values are:".to_owned();
let note_msg = "Make sure all the required values are provided to the handler. For more information, visit <https://docs.rs/dptree/latest/dptree>.".to_owned();
if obligations.iter().any(|(ty, _location)| !container_types.contains(ty)) {
panic!(
"{}\n {}\n{}\n {}\n{}\n {}\n\n{}\n",
if cfg!(test) {
handler_accepts_msg
} else {
handler_accepts_msg.red().bold().to_string()
},
print_types(obligations.keys(), |ty| format!("`{}`", ty.name)),
if cfg!(test) {
provided_types_msg
} else {
provided_types_msg.red().bold().to_string()
},
print_types(&container_types, |ty| format!("`{}`", ty.name)),
if cfg!(test) {
missing_types_msg
} else {
missing_types_msg.red().bold().to_string()
},
print_types(
obligations.iter().filter_map(|(ty, _location)| {
if !container_types.contains(ty) {
Some(ty)
} else {
None
}
}),
|ty| { format!("`{}` from {}", ty.name, obligations[ty]) },
),
note_msg,
);
}
}
}
}
#[cfg(test)]
pub(crate) fn help_inference<Output>(h: Handler<Output>) -> Handler<Output> {
h
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
deps, description, filter_map, filter_map_with_description,
handler::{endpoint, filter, filter_async},
};
use std::{any::Any, collections::HashSet, iter::FromIterator};
use maplit::{btreemap, btreeset, hashset};
#[tokio::test]
async fn test_from_fn_break() {
let input = 123;
let output = "ABC";
let input_types = [Type::of::<i32>()];
let location = Location::caller();
let result = help_inference(from_fn(
|event, _cont: Cont<&'static str>| async move {
assert_eq!(event, deps![input]);
ControlFlow::Break(output)
},
HandlerSignature::Other {
obligations: BTreeMap::from_iter(
input_types.iter().cloned().map(|ty| (ty, location)),
),
guaranteed_outcomes: btreeset! {},
conditional_outcomes: btreeset! {},
continues: false,
},
))
.dispatch(deps![input])
.await;
assert!(result == ControlFlow::Break(output));
}
#[tokio::test]
async fn test_from_fn_continue() {
let input = 123;
type Output = &'static str;
let input_types = [Type::of::<i32>()];
let location = Location::caller();
let result = help_inference(from_fn(
|event, _cont: Cont<&'static str>| async move {
assert_eq!(event, deps![input]);
ControlFlow::<Output, _>::Continue(event)
},
HandlerSignature::Other {
obligations: BTreeMap::from_iter(
input_types.iter().cloned().map(|ty| (ty, location)),
),
guaranteed_outcomes: btreeset! {Type::of::<i32>()},
conditional_outcomes: btreeset! {},
continues: true,
},
))
.dispatch(deps![input])
.await;
assert!(result == ControlFlow::Continue(deps![input]));
}
#[tokio::test]
async fn test_entry() {
let input = 123;
type Output = &'static str;
let result = help_inference(entry::<Output, _>()).dispatch(deps![input]).await;
assert!(result == ControlFlow::Continue(deps![input]));
}
#[tokio::test]
async fn test_execute() {
let input = 123;
let output = "ABC";
let input_types = [Type::of::<i32>()];
let location = Location::caller();
let result = help_inference(from_fn(
|event, cont| {
assert!(event == deps![input]);
cont(event)
},
HandlerSignature::Other {
obligations: BTreeMap::from_iter(
input_types.iter().cloned().map(|ty| (ty, location)),
),
guaranteed_outcomes: btreeset! {Type::of::<i32>()},
conditional_outcomes: btreeset! {},
continues: true,
},
))
.execute(deps![input], |event| async move {
assert!(event == deps![input]);
ControlFlow::Break(output)
})
.await;
assert!(result == ControlFlow::Break(output));
}
#[tokio::test]
async fn test_deeply_nested_tree() {
#[derive(Debug, PartialEq)]
enum Output {
LT,
MinusOne,
Zero,
One,
GT,
}
let negative_handler = filter(|num: i32| num < 0)
.branch(
filter_async(|num: i32| async move { num == -1 })
.endpoint(|| async move { Output::MinusOne }),
)
.branch(endpoint(|| async move { Output::LT }));
let zero_handler = filter_async(|num: i32| async move { num == 0 })
.endpoint(|| async move { Output::Zero });
let positive_handler = filter_async(|num: i32| async move { num > 0 })
.branch(
filter_async(|num: i32| async move { num == 1 })
.endpoint(|| async move { Output::One }),
)
.branch(endpoint(|| async move { Output::GT }));
let dispatcher = help_inference(entry())
.branch(negative_handler)
.branch(zero_handler)
.branch(positive_handler);
assert_eq!(dispatcher.dispatch(deps![2]).await, ControlFlow::Break(Output::GT));
assert_eq!(dispatcher.dispatch(deps![1]).await, ControlFlow::Break(Output::One));
assert_eq!(dispatcher.dispatch(deps![0]).await, ControlFlow::Break(Output::Zero));
assert_eq!(dispatcher.dispatch(deps![-1]).await, ControlFlow::Break(Output::MinusOne));
assert_eq!(dispatcher.dispatch(deps![-2]).await, ControlFlow::Break(Output::LT));
}
#[tokio::test]
async fn allowed_updates() {
use crate::description::{EventKind, InterestSet};
use UpdateKind::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum UpdateKind {
A,
B,
C,
}
impl EventKind for UpdateKind {
fn full_set() -> HashSet<Self> {
hashset! { A, B, C }
}
fn empty_set() -> HashSet<Self> {
hashset! {}
}
}
#[derive(Clone)]
#[allow(dead_code)]
enum Update {
A(i32),
B(u8),
C(u64),
}
fn filter_a<Out>() -> Handler<'static, Out, InterestSet<UpdateKind>>
where
Out: Send + Sync + 'static,
{
filter_map_with_description(
InterestSet::new_filter(hashset! { A }),
|update: Update| match update {
Update::A(x) => Some(x),
_ => None,
},
)
}
fn filter_b<Out>() -> Handler<'static, Out, InterestSet<UpdateKind>>
where
Out: Send + Sync + 'static,
{
filter_map_with_description(
InterestSet::new_filter(hashset! { B }),
|update: Update| match update {
Update::B(x) => Some(x),
_ => None,
},
)
}
fn filter_c<Out>() -> Handler<'static, Out, InterestSet<UpdateKind>>
where
Out: Send + Sync + 'static,
{
filter_map_with_description(
InterestSet::new_filter(hashset! { C }),
|update: Update| match update {
Update::B(x) => Some(x),
_ => None,
},
)
}
fn user_defined_filter<Out>() -> Handler<'static, Out, InterestSet<UpdateKind>>
where
Out: Send + Sync + 'static,
{
filter_map(|update: Update| match update {
Update::B(x) => Some(x),
_ => None,
})
}
#[track_caller]
fn assert(
handler: Handler<'static, (), description::InterestSet<UpdateKind>>,
allowed: HashSet<UpdateKind>,
) {
assert_eq!(handler.description().observed, allowed);
}
assert(filter_a(), hashset! {});
assert(entry().chain(filter_b()), hashset! {});
assert(filter_a().chain(filter_b()), hashset! {});
assert(filter_a().branch(filter_b()), hashset! {});
assert(filter_a().branch(filter_b()).branch(filter_c().chain(filter_c())), hashset! {});
assert(filter_a().chain(filter(|| true)), hashset! { A });
assert(user_defined_filter().chain(filter_a()), hashset! { A, B, C });
assert(filter_a().chain(user_defined_filter()), hashset! { A });
assert(
entry().branch(filter_a()).branch(filter_b()).chain(user_defined_filter()),
hashset! { A, B, C },
);
assert(
entry()
.branch(filter_a().endpoint(|| async {}))
.branch(filter_b().endpoint(|| async {})),
hashset! { A, B },
);
assert(user_defined_filter(), hashset! { A, B, C });
assert(user_defined_filter().branch(filter_a()), hashset! { A, B, C });
assert(entry(), hashset! {});
assert(entry().chain(filter_a().endpoint(|| async {})), hashset! { A });
assert(entry().branch(filter_a()), hashset! {});
assert(filter_a().chain(filter_b()).endpoint(|| async {}), hashset! {});
}
#[tokio::test]
async fn type_check_success() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone)]
struct C;
macro_rules! test {
($($key:ty),*) => {
type_check(
&HandlerSignature::Other {
obligations: btreemap! {
$(Type::of::<$key>() => Location::caller(),)*
},
guaranteed_outcomes: btreeset! {},
conditional_outcomes: btreeset! {},
continues: true,
},
&deps![A, B, C],
&[],
);
};
}
type_check(&HandlerSignature::Entry, &deps![], &[]);
test!(A, B, C);
test!(A, B);
test!(A, C);
test!(B, C);
test!(A);
test!(B);
test!(C);
}
#[test]
#[should_panic(expected = "Your handler accepts the following types:
`dptree::handler::core::tests::type_check_panic::A`
`dptree::handler::core::tests::type_check_panic::B`
`dptree::handler::core::tests::type_check_panic::C`
But only the following values were given to it:
`dptree::handler::core::tests::type_check_panic::A`
`dptree::handler::core::tests::type_check_panic::B`
The missing values are:
`dptree::handler::core::tests::type_check_panic::C` from src/handler/core.rs:4:35
Make sure all the required values are provided to the handler. For more information, visit <https://docs.rs/dptree/latest/dptree>.
")]
fn type_check_panic() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone)]
struct C;
type_check(
&HandlerSignature::Other {
obligations: btreemap! {
Type::of::<A>() => Location::caller(),
Type::of::<B>() => Location::caller(),
Type::of::<C>() => FIXED_LOCATION,
},
guaranteed_outcomes: btreeset! {},
conditional_outcomes: btreeset! {},
continues: true,
},
&deps![A, B],
&[],
);
}
#[test]
fn type_eq_ord_consistent() {
#[derive(Clone)]
struct A;
let ta1 = Type { id: A.type_id(), name: "A1" };
let ta2 = Type { id: A.type_id(), name: "A2" };
assert!(!(ta1 == ta2));
assert!(ta1 < ta2);
assert!(!(ta1 > ta2));
}
#[test]
fn type_btreeset_not_contains_duplicate_name() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
let ta = Type { id: A.type_id(), name: "DuplicateName" };
let tb = Type { id: B.type_id(), name: "DuplicateName" };
let set = btreeset! {ta};
assert!(ta != tb);
assert!(!set.contains(&tb));
}
#[tokio::test]
async fn type_infer_check_chained_combinators() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone)]
struct C;
#[derive(Clone)]
struct D;
#[derive(Clone)]
struct E;
#[derive(Clone)]
struct F;
#[derive(Clone)]
struct G;
#[derive(Clone, Debug, Eq, PartialEq)]
struct H;
let h: Handler<H> = entry()
.map(|/* In the final input types. */ _: A| B)
.inspect(
|/* This type must be removed from the final input types because it is
* provided by the `.map` above. */
_: B,
_: E| (),
)
.filter_map(|/* In the final input types. */ _: C| Some(D))
.filter(
|/* This type is provided by the `.filter_map` above. */ _: D,
_: F| true,
)
.endpoint(
|_: B, _: D, _: G| async { H },
);
let input_types =
[Type::of::<A>(), Type::of::<C>(), Type::of::<E>(), Type::of::<F>(), Type::of::<G>()];
let outcomes = btreeset! {Type::of::<B>(), Type::of::<D>()};
if let HandlerSignature::Other {
obligations: actual_obligations,
guaranteed_outcomes: actual_guaranteed_outcomes,
conditional_outcomes: actual_conditional_outcomes,
continues: _continues,
} = h.sig()
{
assert_eq!(
actual_obligations.keys().collect::<Vec<_>>(),
input_types.iter().collect::<Vec<_>>()
);
let all_outcomes = actual_guaranteed_outcomes
.union(actual_conditional_outcomes)
.cloned()
.collect::<BTreeSet<_>>();
assert_eq!(all_outcomes, outcomes);
} else {
panic!("Expected `HandlerSignature::Other`");
}
let deps = deps![A, C, E, F, G];
type_check(h.sig(), &deps, &[]);
assert_eq!(h.dispatch(deps).await, ControlFlow::Break(H));
}
#[tokio::test]
async fn type_infer_check_branched_combinators() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone)]
struct C;
#[derive(Clone)]
struct D;
#[derive(Clone)]
struct E;
#[derive(Clone, Debug, Eq, PartialEq)]
struct F;
let h: Handler<F> = entry()
.branch(
crate::inspect(|_: A| ()).map(|| B).map(|| C).map(|| D).endpoint(|| async { F }),
)
.branch(crate::inspect(|_: E| ()).map(|| B).map(|| D).endpoint(|| async { F }));
let input_types = btreeset! {Type::of::<A>(), Type::of::<E>()};
let output_types = btreeset! {Type::of::<B>(), Type::of::<C>(), Type::of::<D>()};
if let HandlerSignature::Other {
obligations: actual_obligations,
guaranteed_outcomes: actual_guaranteed_outcomes,
conditional_outcomes: actual_conditional_outcomes,
continues: _continues,
} = h.sig()
{
assert_eq!(
actual_obligations.keys().collect::<Vec<_>>(),
input_types.iter().collect::<Vec<_>>()
);
let all_outcomes = actual_guaranteed_outcomes
.union(actual_conditional_outcomes)
.cloned()
.collect::<BTreeSet<_>>();
assert_eq!(all_outcomes, output_types);
} else {
panic!("Expected `HandlerSignature::Other`");
}
let deps = deps![A, E];
type_check(h.sig(), &deps, &[]);
assert_eq!(h.dispatch(deps).await, ControlFlow::Break(F));
}
#[tokio::test]
async fn obligations_priority() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone, Debug, Eq, PartialEq)]
struct C;
fn test<T: 'static>(h: Handler<C>, column: u32) {
if let HandlerSignature::Other {
obligations,
guaranteed_outcomes: _,
conditional_outcomes: _,
continues: _,
} = h.sig()
{
let (_ty, &location) = obligations
.iter()
.find(|(ty, _location)| ty.id == TypeId::of::<T>())
.expect("Missing obligation");
assert_eq!(location.column(), column);
} else {
panic!("Expected `HandlerSignature::Other`");
}
}
#[rustfmt::skip]
let h: Handler<C> = entry().map(|_: A| ()).endpoint(|_: A, _: B| async { C });
test::<A>(h, 37);
#[rustfmt::skip]
let h: Handler<C> =
entry().branch(crate::map(|_: A| { C })).branch(endpoint(|_: A, _: B| async { C }));
test::<A>(h, 28);
}
#[test]
#[should_panic(expected = "Ill-typed handler chain: the second handler cannot be an entry")]
fn chain_entry() {
let _: Handler<()> = entry().chain(entry());
}
#[test]
#[should_panic(expected = "Ill-typed handler branch: the second handler cannot be an entry")]
fn branch_entry() {
let _: Handler<()> = entry().branch(entry());
}
#[tokio::test]
async fn chain_branch_type_check() {
#[derive(Clone)]
struct A;
let handler: Handler<()> =
entry().chain(crate::map(|| A)).branch(endpoint(|_: A| async {}));
let _no_panic_here: ControlFlow<(), _> = handler.dispatch(deps![]).await;
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
async fn guaranteed_outcomes_chain_success() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
let handler: Handler<()> = entry().map(|| A).map(|_: A| B).endpoint(|_: B| async {});
let result = handler.dispatch(deps![]).await;
assert_eq!(result, ControlFlow::Break(()));
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
async fn conditional_outcomes_chain_success() {
#[derive(Clone)]
struct A;
let handler: Handler<()> = entry().filter_map(|| Some(A)).endpoint(|_: A| async {});
let result = handler.dispatch(deps![]).await;
assert_eq!(result, ControlFlow::Break(()));
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
async fn guaranteed_outcomes_branch_success() {
#[derive(Clone)]
struct A;
let producer = entry().map(|| A);
let handler: Handler<()> = producer.branch(endpoint(|_: A| async {}));
let result = handler.dispatch(deps![]).await;
assert_eq!(result, ControlFlow::Break(()));
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
async fn mixed_outcomes_chain() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
#[derive(Clone)]
struct C;
let handler: Handler<()> = entry()
.map(|| A) .filter_map(|_: A| Some(B)) .map(|_: B| C) .endpoint(|_: C| async {});
let result = handler.dispatch(deps![]).await;
assert_eq!(result, ControlFlow::Break(()));
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
async fn branch_with_guaranteed_continuation() {
#[derive(Clone)]
struct A;
let first_branch = entry().map(|| A).inspect(|_: A| ());
let handler: Handler<()> = first_branch.branch(endpoint(|_: A| async {}));
let result = handler.dispatch(deps![]).await;
assert_eq!(result, ControlFlow::Break(()));
type_check(handler.sig(), &deps![], &[]);
}
#[tokio::test]
#[should_panic(expected = "Your handler accepts the following types:")]
async fn deeply_nested_conditional_failure() {
#[derive(Clone)]
struct A;
#[derive(Clone)]
struct B;
let handler: Handler<()> = entry()
.branch(
entry()
.branch(crate::filter_map(|| Some(A)).endpoint(|| async {}))
.branch(crate::filter_map(|_: A| Some(B)).endpoint(|| async {})),
)
.branch(endpoint(|_: B| async {}));
type_check(handler.sig(), &deps![], &[]);
}
#[test]
#[should_panic(expected = "Dead code detected: since the first handler aborts execution, the \
second handler will never be called.")]
fn chain_endpoint_with_handler() {
let _: Handler<()> = endpoint(|| async {}).endpoint(|| async {});
}
#[test]
#[should_panic(expected = "Dead code detected: since the first handler aborts execution, the \
second handler will never be called.")]
fn chain_endpoint_with_filter() {
let _: Handler<()> = endpoint(|| async {}).filter(|_: i32| true);
}
#[test]
#[should_panic(expected = "Dead code detected: since the first handler aborts execution, the \
second handler will never be called.")]
fn chain_endpoint_with_map() {
let _: Handler<()> = endpoint(|| async {}).map(|| 42);
}
#[test]
#[should_panic(expected = "Dead code detected: since the first handler aborts execution, the \
second handler will never be called.")]
fn chain_complex_endpoint_dead_code() {
let _: Handler<()> = entry()
.chain(filter(|x: i32| x > 0))
.chain(endpoint(|| async {}))
.chain(crate::map(|| "Unreachable"));
}
}