Skip to main content

pacta_executor/
lib.rs

1//! Pacta-native execution abstractions.
2
3#![forbid(unsafe_code)]
4#![warn(missing_docs)]
5
6use pacta_contract::Pact;
7
8pub use pacta_contract::{Outcome, Settlement};
9
10/// A single attempt to fulfill a claimed pact.
11///
12/// This is the executor's designated input seam. It is `#[non_exhaustive]` so it can
13/// gain execution-context fields in a later minor release without a breaking change;
14/// construct it through [`Execution::new`].
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct Execution {
18    /// Pact being executed.
19    pub pact: Pact,
20}
21
22impl Execution {
23    /// Build an execution from a claimed pact.
24    #[must_use]
25    pub fn new(pact: Pact) -> Self {
26        Self { pact }
27    }
28}
29
30/// Public role responsible for executing claimed pacts through middleware.
31pub trait Executor {
32    /// Error returned when the execution infrastructure fails.
33    type Error: std::error::Error;
34
35    /// Execute a claimed pact.
36    fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error>;
37}
38
39/// A Pacta-native decorator over execution: the Tower `Layer` analog. Because `wrap`
40/// takes an `Executor` and returns an `Executor`, middleware compose arbitrarily
41/// (the closure property), which is how orchestration is composed onto the seam.
42pub trait Middleware<E> {
43    /// The wrapped executor type.
44    type Executor: Executor;
45
46    /// Wrap an executor with this middleware.
47    fn wrap(&self, executor: E) -> Self::Executor;
48}
49
50/// The no-op middleware: `wrap` returns the executor unchanged. `Identity` is the
51/// neutral element of composition — the empty stack — so "zero middleware" is a
52/// first-class, holdable value rather than an absence.
53#[derive(Debug, Default, Clone, Copy)]
54pub struct Identity;
55
56impl<E: Executor> Middleware<E> for Identity {
57    type Executor = E;
58
59    fn wrap(&self, executor: E) -> Self::Executor {
60        executor
61    }
62}
63
64/// Two middleware composed into one, reifying the closure property as a value: because
65/// `Stack` is itself a [`Middleware`], a composed stack can be named, stored, and passed
66/// as one middleware *before* an executor exists. `outer` wraps the result of `inner`,
67/// so `outer` is applied last and therefore observes each execution first.
68#[derive(Debug, Default, Clone, Copy)]
69pub struct Stack<Inner, Outer> {
70    inner: Inner,
71    outer: Outer,
72}
73
74impl<Inner, Outer> Stack<Inner, Outer> {
75    /// Compose `inner` and `outer` into one middleware. `outer` wraps `inner`'s result,
76    /// so `outer` observes each execution first.
77    #[must_use]
78    pub const fn new(inner: Inner, outer: Outer) -> Self {
79        Self { inner, outer }
80    }
81}
82
83impl<E, Inner, Outer> Middleware<E> for Stack<Inner, Outer>
84where
85    E: Executor,
86    Inner: Middleware<E>,
87    Outer: Middleware<Inner::Executor>,
88{
89    type Executor = Outer::Executor;
90
91    fn wrap(&self, executor: E) -> Self::Executor {
92        self.outer.wrap(self.inner.wrap(executor))
93    }
94}
95
96/// A blind, ordered assembly of middleware composed over [`Identity`]. It is itself a
97/// [`Middleware`], so applying it is just `wrap`.
98///
99/// It is deliberately *blind*: [`Composition::then`] accepts any `Middleware` through a
100/// single generic operation and inspects nothing, and the type offers no method named for
101/// an orchestration policy (retry, timeout, backoff, circuit, quota, rate-limit) — that
102/// policy is a consumer or sibling concern, never a core convenience.
103///
104/// # Order
105///
106/// Middleware are applied outermost-first in the order added: the **first** middleware
107/// added with [`then`](Composition::then) is the outermost and observes each execution
108/// **first**; the executor is innermost. This mirrors the assembly convention of the
109/// prior art the mechanism is distilled from.
110#[derive(Debug, Default, Clone, Copy)]
111pub struct Composition<M> {
112    middleware: M,
113}
114
115impl Composition<Identity> {
116    /// Start an empty composition over [`Identity`].
117    #[must_use]
118    pub const fn new() -> Self {
119        Self {
120            middleware: Identity,
121        }
122    }
123}
124
125impl<M> Composition<M> {
126    /// Add a middleware to the composition. Blind: it accepts any `Middleware` and
127    /// inspects nothing. The first middleware added is outermost (observes each
128    /// execution first); the executor is innermost.
129    #[must_use]
130    pub fn then<N>(self, next: N) -> Composition<Stack<N, M>> {
131        Composition {
132            middleware: Stack::new(next, self.middleware),
133        }
134    }
135}
136
137impl<E, M> Middleware<E> for Composition<M>
138where
139    E: Executor,
140    M: Middleware<E>,
141{
142    type Executor = M::Executor;
143
144    fn wrap(&self, executor: E) -> Self::Executor {
145        self.middleware.wrap(executor)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[derive(Debug)]
154    struct DummyError;
155
156    impl std::fmt::Display for DummyError {
157        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158            write!(f, "dummy error")
159        }
160    }
161
162    impl std::error::Error for DummyError {}
163
164    struct DummyExecutor;
165    impl Executor for DummyExecutor {
166        type Error = DummyError;
167        fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
168            Ok(Outcome::Fulfilled)
169        }
170    }
171
172    struct IdentityExecutor<E> {
173        inner: E,
174    }
175    impl<E: Executor> Executor for IdentityExecutor<E> {
176        type Error = E::Error;
177        fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
178            self.inner.execute(execution)
179        }
180    }
181
182    struct IdentityMiddleware;
183    impl<E: Executor> Middleware<E> for IdentityMiddleware {
184        type Executor = IdentityExecutor<E>;
185        fn wrap(&self, executor: E) -> Self::Executor {
186            IdentityExecutor { inner: executor }
187        }
188    }
189
190    struct BreachExecutor<E> {
191        _inner: E,
192    }
193    impl<E: Executor> Executor for BreachExecutor<E> {
194        type Error = E::Error;
195        fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
196            Ok(Outcome::Breached)
197        }
198    }
199
200    struct BreachMiddleware;
201    impl<E: Executor> Middleware<E> for BreachMiddleware {
202        type Executor = BreachExecutor<E>;
203        fn wrap(&self, executor: E) -> Self::Executor {
204            BreachExecutor { _inner: executor }
205        }
206    }
207
208    fn dummy_execution() -> Execution {
209        Execution::new(Pact::new(
210            Default::default(),
211            "dummy_docket".to_string(),
212            "dummy_kind".to_string(),
213            vec![],
214        ))
215    }
216
217    #[test]
218    fn identity_middleware_preserves_fulfilled() {
219        let middleware = IdentityMiddleware;
220        let mut executor = middleware.wrap(DummyExecutor);
221        let outcome = executor.execute(dummy_execution()).unwrap();
222        assert_eq!(outcome, Outcome::Fulfilled);
223    }
224
225    #[test]
226    fn breach_middleware_alters_outcome() {
227        let middleware = BreachMiddleware;
228        let mut executor = middleware.wrap(DummyExecutor);
229        let outcome = executor.execute(dummy_execution()).unwrap();
230        assert_eq!(outcome, Outcome::Breached);
231    }
232
233    #[test]
234    fn stacked_middleware_composes() {
235        // The closure property: `Middleware` wraps `Executor` into `Executor`, so
236        // two middleware stack and still yield a working executor. This proves the
237        // "compose the rest" seam holds beyond a single wrap, and guards the
238        // `Middleware<E>` generic shape from regressing so it can no longer stack.
239        let inner = IdentityMiddleware.wrap(DummyExecutor);
240        let mut stacked = IdentityMiddleware.wrap(inner);
241        let outcome = stacked.execute(dummy_execution()).unwrap();
242        assert_eq!(outcome, Outcome::Fulfilled);
243    }
244
245    #[test]
246    fn stacked_middleware_preserves_ordering() {
247        // A breach layer wrapping an identity layer over a fulfilling executor still
248        // composes to a working executor whose outcome is the outermost layer's.
249        let inner = IdentityMiddleware.wrap(DummyExecutor);
250        let mut stacked = BreachMiddleware.wrap(inner);
251        let outcome = stacked.execute(dummy_execution()).unwrap();
252        assert_eq!(outcome, Outcome::Breached);
253    }
254
255    #[test]
256    fn identity_wrap_returns_the_executor_unchanged() {
257        // Identity is the neutral element: wrapping adds nothing.
258        let mut executor = Identity.wrap(DummyExecutor);
259        assert_eq!(
260            executor.execute(dummy_execution()).unwrap(),
261            Outcome::Fulfilled
262        );
263    }
264
265    #[test]
266    fn stack_is_itself_a_middleware() {
267        // Stack reifies the closure property as a value: a composed pair is one Middleware
268        // that wraps an executor into an executor just like a single middleware does.
269        let stack = Stack::new(IdentityMiddleware, BreachMiddleware);
270        let mut executor = stack.wrap(DummyExecutor);
271        // BreachMiddleware is `outer`, so it is applied last and observes execution first.
272        assert_eq!(
273            executor.execute(dummy_execution()).unwrap(),
274            Outcome::Breached
275        );
276    }
277
278    #[test]
279    fn composition_assembles_and_drives_to_a_settlement() {
280        // The blind assembler composes two pass-through middleware over Identity and
281        // drives to a settlement — the reified mechanism proven to compose.
282        let composed = Composition::new()
283            .then(IdentityMiddleware)
284            .then(IdentityMiddleware);
285        let mut executor = composed.wrap(DummyExecutor);
286        assert_eq!(
287            executor.execute(dummy_execution()).unwrap(),
288            Outcome::Fulfilled
289        );
290    }
291
292    #[test]
293    fn composition_orders_first_added_outermost() {
294        use std::cell::RefCell;
295        use std::rc::Rc;
296
297        // A middleware that records both when its layer is *entered* (before delegating inward) and
298        // *exited* (after the inner execution returns), so the full nesting is observable — not just
299        // the final outcome. A regression that inverted the nesting but kept the outcome would still
300        // be caught by comparing the whole trace.
301        struct RecordingExecutor<E> {
302            inner: E,
303            label: &'static str,
304            log: Rc<RefCell<Vec<String>>>,
305        }
306        impl<E: Executor> Executor for RecordingExecutor<E> {
307            type Error = E::Error;
308            fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
309                self.log.borrow_mut().push(format!("{}:enter", self.label));
310                let outcome = self.inner.execute(execution);
311                self.log.borrow_mut().push(format!("{}:exit", self.label));
312                outcome
313            }
314        }
315        struct Recorder {
316            label: &'static str,
317            log: Rc<RefCell<Vec<String>>>,
318        }
319        impl<E: Executor> Middleware<E> for Recorder {
320            type Executor = RecordingExecutor<E>;
321            fn wrap(&self, inner: E) -> Self::Executor {
322                RecordingExecutor {
323                    inner,
324                    label: self.label,
325                    log: Rc::clone(&self.log),
326                }
327            }
328        }
329
330        // The innermost executor records that it ran, so the trace shows the executor is innermost.
331        struct RecordingInner {
332            log: Rc<RefCell<Vec<String>>>,
333        }
334        impl Executor for RecordingInner {
335            type Error = DummyError;
336            fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
337                self.log.borrow_mut().push("executor".to_string());
338                Ok(Outcome::Fulfilled)
339            }
340        }
341
342        let log: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
343        let composed = Composition::new()
344            .then(Recorder {
345                label: "first",
346                log: Rc::clone(&log),
347            })
348            .then(Recorder {
349                label: "second",
350                log: Rc::clone(&log),
351            });
352        let mut executor = composed.wrap(RecordingInner {
353            log: Rc::clone(&log),
354        });
355        executor.execute(dummy_execution()).unwrap();
356
357        // Full trace: the first-added middleware is outermost — entered first, exited last; the
358        // second is nested within it; the executor is innermost.
359        assert_eq!(
360            *log.borrow(),
361            vec![
362                "first:enter",
363                "second:enter",
364                "executor",
365                "second:exit",
366                "first:exit",
367            ]
368        );
369    }
370}