Skip to main content

runifold_effect/
handler.rs

1use std::{future::Future, pin::Pin};
2
3use runifold_core::{CancellationToken, EffectRequest, Instant, RunContext, RunError, RunId};
4use serde_json::Value;
5
6/// Boxed future returned by an effect handler.
7#[cfg(not(target_arch = "wasm32"))]
8pub type EffectFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
9
10/// Boxed effect-handler future on single-threaded WASM.
11#[cfg(target_arch = "wasm32")]
12pub type EffectFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
13
14/// Lifecycle-only context passed to an external-effect handler.
15#[derive(Clone, Debug)]
16pub struct EffectExecutionContext {
17    run_id: RunId,
18    deadline: Option<Instant>,
19    cancellation: CancellationToken,
20}
21
22impl EffectExecutionContext {
23    pub(crate) fn for_run(run: &RunContext) -> Self {
24        Self {
25            run_id: run.run_id(),
26            deadline: run.deadline(),
27            cancellation: run.cancellation().child_token(),
28        }
29    }
30
31    /// Returns the owning Run identity.
32    pub const fn run_id(&self) -> RunId {
33        self.run_id
34    }
35
36    /// Returns the effective deadline.
37    pub const fn deadline(&self) -> Option<Instant> {
38        self.deadline
39    }
40
41    /// Returns the descendant cancellation token.
42    pub const fn cancellation(&self) -> &CancellationToken {
43        &self.cancellation
44    }
45}
46
47/// Object-safe implementation boundary for one class of external effect.
48pub trait EffectHandler: Send + Sync {
49    /// Executes a prepared effect request.
50    fn execute(
51        &self,
52        request: &EffectRequest,
53        context: EffectExecutionContext,
54    ) -> EffectFuture<'_, Result<Value, RunError>>;
55}
56
57/// Result of querying an external system for an ambiguously started effect.
58#[derive(Clone, Debug, PartialEq)]
59#[non_exhaustive]
60pub enum EffectReconciliation {
61    /// The remote operation completed and this is its canonical output.
62    Completed(Value),
63    /// The remote system proves the operation did not execute.
64    NotExecuted,
65    /// The remote system cannot determine the outcome safely.
66    Ambiguous,
67}
68
69/// Optional remote-state boundary for resolving started effects after a crash.
70///
71/// Implementations should query by the request's stable idempotency key or an
72/// equivalent remote operation identity. They must not perform the effect.
73pub trait EffectReconciler: Send + Sync {
74    /// Queries the external system for the effect's durable outcome.
75    fn reconcile(
76        &self,
77        request: &EffectRequest,
78        context: EffectExecutionContext,
79    ) -> EffectFuture<'_, Result<EffectReconciliation, RunError>>;
80}