future_form 0.3.1

Abstractions over Send and !Send futures
Documentation
use super::*;

// Trait parameterized by K — models keyhive's AsyncSigner<K> pattern
trait AsyncOp<K: FutureForm> {
    fn run<'a>(&'a self, input: &'a str) -> K::Future<'a, String>;
}

struct EchoOp;

impl AsyncOp<Sendable> for EchoOp {
    fn run<'a>(&'a self, input: &'a str) -> BoxFuture<'a, String> {
        let s = input.to_string();
        Sendable::from_future(async move { s })
    }
}

impl AsyncOp<Local> for EchoOp {
    fn run<'a>(&'a self, input: &'a str) -> LocalBoxFuture<'a, String> {
        let s = input.to_string();
        Local::from_future(async move { s })
    }
}

// K-referencing bound as INLINE generic param bound, not in where clause.
// This is the pattern that was previously broken: `S: AsyncOp<K>` inline
// would survive as `S: AsyncOp<K>` after K was removed from the param list.
trait Pipeline<K: FutureForm> {
    fn execute<'a>(&'a self, input: &'a str) -> K::Future<'a, String>;
}

struct SimplePipeline<S> {
    op: S,
}

#[future_form(Sendable, Local)]
impl<K: FutureForm, S: AsyncOp<K>> Pipeline<K> for SimplePipeline<S> {
    fn execute<'a>(&'a self, input: &'a str) -> K::Future<'a, String> {
        self.op.run(input)
    }
}

#[tokio::test]
async fn test_inline_k_bound_sendable() {
    let pipeline = SimplePipeline { op: EchoOp };
    let result = <SimplePipeline<EchoOp> as Pipeline<Sendable>>::execute(&pipeline, "hello").await;
    assert_eq!(result, "hello");
}

#[tokio::test]
async fn test_inline_k_bound_local() {
    let pipeline = SimplePipeline { op: EchoOp };
    let result = <SimplePipeline<EchoOp> as Pipeline<Local>>::execute(&pipeline, "hello").await;
    assert_eq!(result, "hello");
}

// Multiple inline K-referencing bounds
trait Listener<K: FutureForm> {
    #[allow(dead_code)]
    fn notify(&self) -> K::Future<'_, ()>;
}

struct NoopListener;

impl Listener<Sendable> for NoopListener {
    fn notify(&self) -> BoxFuture<'_, ()> {
        Sendable::from_future(async {})
    }
}

impl Listener<Local> for NoopListener {
    fn notify(&self) -> LocalBoxFuture<'_, ()> {
        Local::from_future(async {})
    }
}

trait Orchestrator<K: FutureForm> {
    fn process<'a>(&'a self, input: &'a str) -> K::Future<'a, String>;
}

struct FullOrchestrator<S, L> {
    op: S,
    _listener: L,
}

// Both S and L have inline K-referencing bounds
#[future_form(Sendable, Local)]
impl<K: FutureForm, S: AsyncOp<K>, L: Listener<K>> Orchestrator<K> for FullOrchestrator<S, L> {
    fn process<'a>(&'a self, input: &'a str) -> K::Future<'a, String> {
        self.op.run(input)
    }
}

#[tokio::test]
async fn test_multiple_inline_k_bounds_sendable() {
    let orch = FullOrchestrator {
        op: EchoOp,
        _listener: NoopListener,
    };
    let result =
        <FullOrchestrator<EchoOp, NoopListener> as Orchestrator<Sendable>>::process(&orch, "world")
            .await;
    assert_eq!(result, "world");
}

#[tokio::test]
async fn test_multiple_inline_k_bounds_local() {
    let orch = FullOrchestrator {
        op: EchoOp,
        _listener: NoopListener,
    };
    let result =
        <FullOrchestrator<EchoOp, NoopListener> as Orchestrator<Local>>::process(&orch, "world")
            .await;
    assert_eq!(result, "world");
}

// Mixed: some K-referencing bounds inline, some in where clause
trait MixedBounds<K: FutureForm> {
    fn run<'a>(&'a self, input: &'a str) -> K::Future<'a, String>;
}

struct MixedPipeline<S, L> {
    op: S,
    _listener: L,
}

#[future_form(Sendable, Local)]
impl<K: FutureForm, S: AsyncOp<K>, L> MixedBounds<K> for MixedPipeline<S, L>
where
    L: Listener<K>,
{
    fn run<'a>(&'a self, input: &'a str) -> K::Future<'a, String> {
        self.op.run(input)
    }
}

#[tokio::test]
async fn test_mixed_inline_and_where_k_bounds_sendable() {
    let p = MixedPipeline {
        op: EchoOp,
        _listener: NoopListener,
    };
    let result =
        <MixedPipeline<EchoOp, NoopListener> as MixedBounds<Sendable>>::run(&p, "mixed").await;
    assert_eq!(result, "mixed");
}

#[tokio::test]
async fn test_mixed_inline_and_where_k_bounds_local() {
    let p = MixedPipeline {
        op: EchoOp,
        _listener: NoopListener,
    };
    let result =
        <MixedPipeline<EchoOp, NoopListener> as MixedBounds<Local>>::run(&p, "mixed").await;
    assert_eq!(result, "mixed");
}