#![forbid(unsafe_code)]
#![warn(missing_docs)]
use pacta_contract::Pact;
pub use pacta_contract::{Outcome, Settlement};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Execution {
pub pact: Pact,
}
impl Execution {
#[must_use]
pub fn new(pact: Pact) -> Self {
Self { pact }
}
}
pub trait Executor {
type Error: std::error::Error;
fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error>;
}
pub trait Middleware<E> {
type Executor: Executor;
fn wrap(&self, executor: E) -> Self::Executor;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Identity;
impl<E: Executor> Middleware<E> for Identity {
type Executor = E;
fn wrap(&self, executor: E) -> Self::Executor {
executor
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Stack<Inner, Outer> {
inner: Inner,
outer: Outer,
}
impl<Inner, Outer> Stack<Inner, Outer> {
#[must_use]
pub const fn new(inner: Inner, outer: Outer) -> Self {
Self { inner, outer }
}
}
impl<E, Inner, Outer> Middleware<E> for Stack<Inner, Outer>
where
E: Executor,
Inner: Middleware<E>,
Outer: Middleware<Inner::Executor>,
{
type Executor = Outer::Executor;
fn wrap(&self, executor: E) -> Self::Executor {
self.outer.wrap(self.inner.wrap(executor))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Composition<M> {
middleware: M,
}
impl Composition<Identity> {
#[must_use]
pub const fn new() -> Self {
Self {
middleware: Identity,
}
}
}
impl<M> Composition<M> {
#[must_use]
pub fn then<N>(self, next: N) -> Composition<Stack<N, M>> {
Composition {
middleware: Stack::new(next, self.middleware),
}
}
}
impl<E, M> Middleware<E> for Composition<M>
where
E: Executor,
M: Middleware<E>,
{
type Executor = M::Executor;
fn wrap(&self, executor: E) -> Self::Executor {
self.middleware.wrap(executor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct DummyError;
impl std::fmt::Display for DummyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "dummy error")
}
}
impl std::error::Error for DummyError {}
struct DummyExecutor;
impl Executor for DummyExecutor {
type Error = DummyError;
fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
Ok(Outcome::Fulfilled)
}
}
struct IdentityExecutor<E> {
inner: E,
}
impl<E: Executor> Executor for IdentityExecutor<E> {
type Error = E::Error;
fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
self.inner.execute(execution)
}
}
struct IdentityMiddleware;
impl<E: Executor> Middleware<E> for IdentityMiddleware {
type Executor = IdentityExecutor<E>;
fn wrap(&self, executor: E) -> Self::Executor {
IdentityExecutor { inner: executor }
}
}
struct BreachExecutor<E> {
_inner: E,
}
impl<E: Executor> Executor for BreachExecutor<E> {
type Error = E::Error;
fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
Ok(Outcome::Breached)
}
}
struct BreachMiddleware;
impl<E: Executor> Middleware<E> for BreachMiddleware {
type Executor = BreachExecutor<E>;
fn wrap(&self, executor: E) -> Self::Executor {
BreachExecutor { _inner: executor }
}
}
fn dummy_execution() -> Execution {
Execution::new(Pact::new(
Default::default(),
"dummy_docket".to_string(),
"dummy_kind".to_string(),
vec![],
))
}
#[test]
fn identity_middleware_preserves_fulfilled() {
let middleware = IdentityMiddleware;
let mut executor = middleware.wrap(DummyExecutor);
let outcome = executor.execute(dummy_execution()).unwrap();
assert_eq!(outcome, Outcome::Fulfilled);
}
#[test]
fn breach_middleware_alters_outcome() {
let middleware = BreachMiddleware;
let mut executor = middleware.wrap(DummyExecutor);
let outcome = executor.execute(dummy_execution()).unwrap();
assert_eq!(outcome, Outcome::Breached);
}
#[test]
fn stacked_middleware_composes() {
let inner = IdentityMiddleware.wrap(DummyExecutor);
let mut stacked = IdentityMiddleware.wrap(inner);
let outcome = stacked.execute(dummy_execution()).unwrap();
assert_eq!(outcome, Outcome::Fulfilled);
}
#[test]
fn stacked_middleware_preserves_ordering() {
let inner = IdentityMiddleware.wrap(DummyExecutor);
let mut stacked = BreachMiddleware.wrap(inner);
let outcome = stacked.execute(dummy_execution()).unwrap();
assert_eq!(outcome, Outcome::Breached);
}
#[test]
fn identity_wrap_returns_the_executor_unchanged() {
let mut executor = Identity.wrap(DummyExecutor);
assert_eq!(
executor.execute(dummy_execution()).unwrap(),
Outcome::Fulfilled
);
}
#[test]
fn stack_is_itself_a_middleware() {
let stack = Stack::new(IdentityMiddleware, BreachMiddleware);
let mut executor = stack.wrap(DummyExecutor);
assert_eq!(
executor.execute(dummy_execution()).unwrap(),
Outcome::Breached
);
}
#[test]
fn composition_assembles_and_drives_to_a_settlement() {
let composed = Composition::new()
.then(IdentityMiddleware)
.then(IdentityMiddleware);
let mut executor = composed.wrap(DummyExecutor);
assert_eq!(
executor.execute(dummy_execution()).unwrap(),
Outcome::Fulfilled
);
}
#[test]
fn composition_orders_first_added_outermost() {
use std::cell::RefCell;
use std::rc::Rc;
struct RecordingExecutor<E> {
inner: E,
label: &'static str,
log: Rc<RefCell<Vec<String>>>,
}
impl<E: Executor> Executor for RecordingExecutor<E> {
type Error = E::Error;
fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
self.log.borrow_mut().push(format!("{}:enter", self.label));
let outcome = self.inner.execute(execution);
self.log.borrow_mut().push(format!("{}:exit", self.label));
outcome
}
}
struct Recorder {
label: &'static str,
log: Rc<RefCell<Vec<String>>>,
}
impl<E: Executor> Middleware<E> for Recorder {
type Executor = RecordingExecutor<E>;
fn wrap(&self, inner: E) -> Self::Executor {
RecordingExecutor {
inner,
label: self.label,
log: Rc::clone(&self.log),
}
}
}
struct RecordingInner {
log: Rc<RefCell<Vec<String>>>,
}
impl Executor for RecordingInner {
type Error = DummyError;
fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
self.log.borrow_mut().push("executor".to_string());
Ok(Outcome::Fulfilled)
}
}
let log: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
let composed = Composition::new()
.then(Recorder {
label: "first",
log: Rc::clone(&log),
})
.then(Recorder {
label: "second",
log: Rc::clone(&log),
});
let mut executor = composed.wrap(RecordingInner {
log: Rc::clone(&log),
});
executor.execute(dummy_execution()).unwrap();
assert_eq!(
*log.borrow(),
vec![
"first:enter",
"second:enter",
"executor",
"second:exit",
"first:exit",
]
);
}
}