Skip to main content

varar_core/
handler.rs

1//! Step handlers — the Rust replacement for Java's reflective arity-matched SAM
2//! invocation (`Execute.invokeHandler`/`samMethod`). A handler is a boxed closure
3//! over `(state, args)`; arity is validated at the constructor. `StepReturn`
4//! carries the sync-or-`Future` channel (the analog of "an `Object` that might be
5//! a `CompletableFuture`").
6
7use crate::error::HandlerError;
8use crate::value::Value;
9use std::any::Any;
10use std::future::Future;
11use std::pin::Pin;
12use std::rc::Rc;
13
14/// A handler's resolved return: `Ok(None)` = "no assertion" (Java `null`),
15/// `Ok(Some(v))` = a value, `Err(_)` = an author-signalled failure (Java `throw`).
16pub type HandlerReturn = Result<StepOutput, HandlerError>;
17
18/// What a handler produced. A stimulus yields the whole next state, which is
19/// **opaque to the core** — it is threaded between a file's steps and replaced
20/// wholesale, never compared, serialized, or inspected. A sensor yields the
21/// value to compare against the document (`None` = no assertion).
22pub enum StepOutput {
23    State(Rc<dyn Any>),
24    Compared(Option<Value>),
25}
26
27impl StepOutput {
28    /// The comparison value, or `None` for a state output.
29    pub fn compared(&self) -> Option<&Value> {
30        match self {
31            StepOutput::Compared(v) => v.as_ref(),
32            StepOutput::State(_) => None,
33        }
34    }
35}
36
37/// The sync-or-async return channel.
38pub enum StepReturn {
39    Ready(HandlerReturn),
40    Pending(Pin<Box<dyn Future<Output = HandlerReturn>>>),
41}
42
43/// Reads an opaque state back as a [`Value`], defaulting to `Null`.
44fn value_state(state: &Rc<dyn Any>) -> Value {
45    state
46        .downcast_ref::<Value>()
47        .cloned()
48        .unwrap_or(Value::Null)
49}
50
51/// The boxed closure a [`Handler`] wraps: the opaque state plus the slot
52/// arguments, in slot order.
53type HandlerFn = dyn Fn(Rc<dyn Any>, Vec<Value>) -> StepReturn;
54
55/// A registered step handler: a closure over `(state, args_after_state)`.
56#[derive(Clone)]
57pub struct Handler {
58    f: Rc<HandlerFn>,
59}
60
61impl Handler {
62    /// A no-op handler (arity-agnostic) — used where a handler is never invoked.
63    pub fn noop() -> Handler {
64        Handler {
65            f: Rc::new(|_state, _args| StepReturn::Ready(Ok(StepOutput::Compared(None)))),
66        }
67    }
68
69    /// Builds a handler from a raw closure over the opaque state and the slot
70    /// arguments. The facade's typed `stimulus`/`sensor` build these; authors
71    /// use those instead.
72    pub fn new(f: impl Fn(Rc<dyn Any>, Vec<Value>) -> HandlerReturn + 'static) -> Handler {
73        Handler {
74            f: Rc::new(move |state, args| StepReturn::Ready(f(state, args))),
75        }
76    }
77
78    /// Fixed-arity conveniences over a [`Value`] state. These exist for the
79    /// core's own tests and for any consumer not using the `varar` facade —
80    /// the facade builds handlers from typed closures instead, so an author
81    /// never calls these.
82    pub fn sync0(f: impl Fn(Value) -> Result<Option<Value>, HandlerError> + 'static) -> Handler {
83        Handler::new(move |state, args| {
84            if !args.is_empty() {
85                return Err(HandlerError::new("no handler with 0 parameter(s)"));
86            }
87            Ok(StepOutput::Compared(f(value_state(&state))?))
88        })
89    }
90
91    /// A synchronous 1-argument handler `(state, a)`.
92    pub fn sync1(
93        f: impl Fn(Value, Value) -> Result<Option<Value>, HandlerError> + 'static,
94    ) -> Handler {
95        Handler::new(move |state, args| {
96            if args.len() != 1 {
97                return Err(HandlerError::new("no handler with 1 parameter(s)"));
98            }
99            Ok(StepOutput::Compared(f(value_state(&state), args[0].clone())?))
100        })
101    }
102
103    /// A synchronous 2-argument handler `(state, a, b)`.
104    pub fn sync2(
105        f: impl Fn(Value, Value, Value) -> Result<Option<Value>, HandlerError> + 'static,
106    ) -> Handler {
107        Handler::new(move |state, args| {
108            if args.len() != 2 {
109                return Err(HandlerError::new("no handler with 2 parameter(s)"));
110            }
111            Ok(StepOutput::Compared(f(value_state(&state), args[0].clone(), args[1].clone())?))
112        })
113    }
114
115    /// As [`Handler::sync1`], but its result is the next state rather than a
116    /// value to compare.
117    pub fn state1(f: impl Fn(Value, Value) -> Result<Value, HandlerError> + 'static) -> Handler {
118        Handler::new(move |state, args| {
119            if args.len() != 1 {
120                return Err(HandlerError::new("no handler with 1 parameter(s)"));
121            }
122            Ok(StepOutput::State(Rc::new(f(value_state(&state), args[0].clone())?)))
123        })
124    }
125
126    /// An asynchronous 0-argument handler returning a `Future`.
127    pub fn async0(
128        f: impl Fn(Value) -> Pin<Box<dyn Future<Output = HandlerReturn>>> + 'static,
129    ) -> Handler {
130        Handler {
131            f: Rc::new(move |state, args| {
132                if !args.is_empty() {
133                    return StepReturn::Ready(Err(HandlerError::new(
134                        "no handler with 0 parameter(s)",
135                    )));
136                }
137                StepReturn::Pending(f(value_state(&state)))
138            }),
139        }
140    }
141
142    /// A synchronous handler of any arity: `(state, args)`.
143    pub fn sync_var(
144        f: impl Fn(Value, Vec<Value>) -> Result<Option<Value>, HandlerError> + 'static,
145    ) -> Handler {
146        Handler::new(move |state, args| Ok(StepOutput::Compared(f(value_state(&state), args)?)))
147    }
148
149    /// As [`Handler::sync_var`], returning a `Future`.
150    pub fn async_var(
151        f: impl Fn(Value, Vec<Value>) -> Pin<Box<dyn Future<Output = HandlerReturn>>> + 'static,
152    ) -> Handler {
153        Handler {
154            f: Rc::new(move |state, args| StepReturn::Pending(f(value_state(&state), args))),
155        }
156    }
157
158    /// Invokes the handler with `state` + `args` (captures then trailing attachment).
159    pub(crate) fn call(&self, state: Rc<dyn Any>, args: Vec<Value>) -> StepReturn {
160        (self.f)(state, args)
161    }
162}