use std::{fmt, pin::Pin};
use crate::context::Cx;
pub type BoxCxFuture<'cx, R> = Pin<Box<dyn Future<Output = R> + Send + 'cx>>;
macro_rules! cx_fn {
(
$future:ident,
$(#[$bound_meta:meta])*
$bound:ident,
$(#[$boxed_meta:meta])*
$boxed:ident,
($($arg:ident: $ty:ident),*)
) => {
#[doc = concat!("[`Cx`], so [`", stringify!($bound), "`] can require it to be `Send`.")]
#[doc(hidden)]
pub trait $future<'cx, $($ty,)* R>:
Fn(&'cx Cx $(, $ty)*) -> <Self as $future<'cx, $($ty,)* R>>::Fut
{
type Fut: Future<Output = R> + Send + 'cx;
}
impl<'cx, F, Fut, $($ty,)* R> $future<'cx, $($ty,)* R> for F
where
F: Fn(&'cx Cx $(, $ty)*) -> Fut,
Fut: Future<Output = R> + Send + 'cx,
{
type Fut = Fut;
}
$(#[$bound_meta])*
pub trait $bound<$($ty,)* R>:
AsyncFn(&Cx $(, $ty)*) -> R
+ for<'cx> $future<'cx, $($ty,)* R>
+ Send
+ Sync
+ 'static
{
}
impl<F, $($ty,)* R> $bound<$($ty,)* R> for F where
F: AsyncFn(&Cx $(, $ty)*) -> R
+ for<'cx> $future<'cx, $($ty,)* R>
+ Send
+ Sync
+ 'static
{
}
$(#[$boxed_meta])*
pub struct $boxed<$($ty,)* R>(
Box<dyn for<'cx> Fn(&'cx Cx $(, $ty)*) -> BoxCxFuture<'cx, R> + Send + Sync>,
);
impl<$($ty: 'static,)* R: 'static> $boxed<$($ty,)* R> {
pub fn new(f: impl $bound<$($ty,)* R>) -> Self {
Self(Box::new(move |cx $(, $arg)*| Box::pin(f(cx $(, $arg)*))))
}
pub fn boxed(
f: impl for<'cx> Fn(&'cx Cx $(, $ty)*) -> BoxCxFuture<'cx, R>
+ Send
+ Sync
+ 'static,
) -> Self {
Self(Box::new(f))
}
pub fn call<'cx>(&self, cx: &'cx Cx $(, $arg: $ty)*) -> BoxCxFuture<'cx, R> {
(self.0)(cx $(, $arg)*)
}
}
impl<$($ty,)* R> fmt::Debug for $boxed<$($ty,)* R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($boxed)).finish_non_exhaustive()
}
}
};
}
cx_fn!(
CxFnFuture,
AsyncCxFn,
BoxCxFn,
()
);
cx_fn!(
CxFnFuture1,
AsyncCxFn1,
BoxCxFn1,
(a: A)
);
cx_fn!(
CxFnFuture2,
AsyncCxFn2,
BoxCxFn2,
(a: A, b: B)
);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
context::context,
error::{Error, Result},
};
struct Name(&'static str);
fn named(name: &'static str) -> Cx {
Cx::new().with(Name(name))
}
async fn greet(cx: &Cx) -> Result<String> {
Ok(format!("hello {}", context::<Name>(cx).0))
}
#[tokio::test]
async fn calls_an_async_closure_with_the_given_context() {
let f = BoxCxFn::new(async |cx| context::<Name>(cx).0.len());
assert_eq!(f.call(&named("ada")).await, 3);
assert_eq!(f.call(&named("grace")).await, 5);
}
#[tokio::test]
async fn calls_an_async_fn() {
let f = BoxCxFn::new(greet);
assert_eq!(f.call(&named("ada")).await.unwrap(), "hello ada");
}
#[tokio::test]
async fn propagates_errors_from_a_closure_using_the_question_mark() {
let f: BoxCxFn<Result<String>> = BoxCxFn::new(async |cx| {
let greeting = greet(cx).await?;
Err(Error::msg(greeting))
});
assert!(f.call(&named("ada")).await.is_err());
}
#[tokio::test]
async fn passes_extra_arguments() {
let one = BoxCxFn1::new(async |cx, times: usize| context::<Name>(cx).0.repeat(times));
assert_eq!(one.call(&named("ab"), 2).await, "abab");
let two = BoxCxFn2::new(async |cx, prefix: &'static str, suffix: char| {
format!("{prefix}{}{suffix}", context::<Name>(cx).0)
});
assert_eq!(two.call(&named("ada"), "<", '>').await, "<ada>");
}
#[tokio::test]
async fn boxed_accepts_a_function_that_captures_state() {
let suffix = String::from("!");
let f = BoxCxFn::boxed(move |cx| {
let suffix = suffix.clone();
Box::pin(async move { format!("{}{suffix}", context::<Name>(cx).0) })
});
assert_eq!(f.call(&named("ada")).await, "ada!");
}
#[test]
fn stored_functions_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<BoxCxFn<Result<String>>>();
assert_send_sync::<BoxCxFn2<u64, String, Option<String>>>();
}
}