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#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[derive(Debug)]
55    struct DummyError;
56
57    impl std::fmt::Display for DummyError {
58        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59            write!(f, "dummy error")
60        }
61    }
62
63    impl std::error::Error for DummyError {}
64
65    struct DummyExecutor;
66    impl Executor for DummyExecutor {
67        type Error = DummyError;
68        fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
69            Ok(Outcome::Fulfilled)
70        }
71    }
72
73    struct IdentityExecutor<E> {
74        inner: E,
75    }
76    impl<E: Executor> Executor for IdentityExecutor<E> {
77        type Error = E::Error;
78        fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
79            self.inner.execute(execution)
80        }
81    }
82
83    struct IdentityMiddleware;
84    impl<E: Executor> Middleware<E> for IdentityMiddleware {
85        type Executor = IdentityExecutor<E>;
86        fn wrap(&self, executor: E) -> Self::Executor {
87            IdentityExecutor { inner: executor }
88        }
89    }
90
91    struct BreachExecutor<E> {
92        _inner: E,
93    }
94    impl<E: Executor> Executor for BreachExecutor<E> {
95        type Error = E::Error;
96        fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
97            Ok(Outcome::Breached)
98        }
99    }
100
101    struct BreachMiddleware;
102    impl<E: Executor> Middleware<E> for BreachMiddleware {
103        type Executor = BreachExecutor<E>;
104        fn wrap(&self, executor: E) -> Self::Executor {
105            BreachExecutor { _inner: executor }
106        }
107    }
108
109    fn dummy_execution() -> Execution {
110        Execution::new(Pact::new(
111            Default::default(),
112            "dummy_docket".to_string(),
113            "dummy_kind".to_string(),
114            vec![],
115        ))
116    }
117
118    #[test]
119    fn identity_middleware_preserves_fulfilled() {
120        let middleware = IdentityMiddleware;
121        let mut executor = middleware.wrap(DummyExecutor);
122        let outcome = executor.execute(dummy_execution()).unwrap();
123        assert_eq!(outcome, Outcome::Fulfilled);
124    }
125
126    #[test]
127    fn breach_middleware_alters_outcome() {
128        let middleware = BreachMiddleware;
129        let mut executor = middleware.wrap(DummyExecutor);
130        let outcome = executor.execute(dummy_execution()).unwrap();
131        assert_eq!(outcome, Outcome::Breached);
132    }
133
134    #[test]
135    fn stacked_middleware_composes() {
136        // The closure property: `Middleware` wraps `Executor` into `Executor`, so
137        // two middleware stack and still yield a working executor. This proves the
138        // "compose the rest" seam holds beyond a single wrap, and guards the
139        // `Middleware<E>` generic shape from regressing so it can no longer stack.
140        let inner = IdentityMiddleware.wrap(DummyExecutor);
141        let mut stacked = IdentityMiddleware.wrap(inner);
142        let outcome = stacked.execute(dummy_execution()).unwrap();
143        assert_eq!(outcome, Outcome::Fulfilled);
144    }
145
146    #[test]
147    fn stacked_middleware_preserves_ordering() {
148        // A breach layer wrapping an identity layer over a fulfilling executor still
149        // composes to a working executor whose outcome is the outermost layer's.
150        let inner = IdentityMiddleware.wrap(DummyExecutor);
151        let mut stacked = BreachMiddleware.wrap(inner);
152        let outcome = stacked.execute(dummy_execution()).unwrap();
153        assert_eq!(outcome, Outcome::Breached);
154    }
155}