#![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>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Continue,
Concede,
}
pub trait Policy<E> {
fn decide(&self, attempts: u32, error: &E) -> Verdict;
}
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 std::collections::HashMap;
use pacta_contract::Uuid;
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",
]
);
}
#[derive(Clone, Copy)]
struct FixedThreshold {
threshold: u32,
}
impl Policy<DummyError> for FixedThreshold {
fn decide(&self, attempts: u32, _error: &DummyError) -> Verdict {
if attempts >= self.threshold {
Verdict::Concede
} else {
Verdict::Continue
}
}
}
struct FailingExecutor;
impl Executor for FailingExecutor {
type Error = DummyError;
fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
Err(DummyError)
}
}
struct GiveUpExecutor<Inner, P> {
inner: Inner,
policy: P,
attempts: HashMap<Uuid, u32>,
}
impl<Inner, P> Executor for GiveUpExecutor<Inner, P>
where
Inner: Executor,
P: Policy<Inner::Error>,
{
type Error = Inner::Error;
fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
let id = execution.pact.id;
match self.inner.execute(execution) {
Ok(outcome) => {
self.attempts.remove(&id);
Ok(outcome)
}
Err(error) => {
let attempts = self.attempts.entry(id).or_insert(0);
*attempts += 1;
match self.policy.decide(*attempts, &error) {
Verdict::Continue => Err(error),
Verdict::Concede => Ok(Outcome::Breached),
}
}
}
}
}
struct GiveUp<P> {
policy: P,
}
impl<E, P> Middleware<E> for GiveUp<P>
where
E: Executor,
P: Policy<E::Error> + Clone,
{
type Executor = GiveUpExecutor<E, P>;
fn wrap(&self, executor: E) -> Self::Executor {
GiveUpExecutor {
inner: executor,
policy: self.policy.clone(),
attempts: HashMap::new(),
}
}
}
fn execution_with_id(id: Uuid) -> Execution {
Execution::new(Pact::new(
id,
"dummy_docket".to_string(),
"dummy_kind".to_string(),
vec![],
))
}
#[test]
fn below_threshold_keeps_propagating_the_error() {
let mut executor = GiveUp {
policy: FixedThreshold { threshold: 3 },
}
.wrap(FailingExecutor);
let id = Uuid::from_u128(1);
assert!(executor.execute(execution_with_id(id)).is_err());
assert!(executor.execute(execution_with_id(id)).is_err());
}
#[test]
fn reaching_threshold_concedes_as_breached() {
let mut executor = GiveUp {
policy: FixedThreshold { threshold: 3 },
}
.wrap(FailingExecutor);
let id = Uuid::from_u128(2);
assert!(executor.execute(execution_with_id(id)).is_err());
assert!(executor.execute(execution_with_id(id)).is_err());
assert_eq!(
executor.execute(execution_with_id(id)).unwrap(),
Outcome::Breached
);
}
#[test]
fn a_success_resets_the_failure_count() {
struct FlakyThenFulfilling {
fail_next: bool,
}
impl Executor for FlakyThenFulfilling {
type Error = DummyError;
fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
if self.fail_next {
Err(DummyError)
} else {
Ok(Outcome::Fulfilled)
}
}
}
let mut executor = GiveUp {
policy: FixedThreshold { threshold: 2 },
}
.wrap(FlakyThenFulfilling { fail_next: true });
let id = Uuid::from_u128(3);
assert!(executor.execute(execution_with_id(id)).is_err());
executor.inner.fail_next = false;
assert_eq!(
executor.execute(execution_with_id(id)).unwrap(),
Outcome::Fulfilled
);
executor.inner.fail_next = true;
assert!(executor.execute(execution_with_id(id)).is_err());
}
#[test]
fn distinct_pacts_are_tracked_independently() {
let mut executor = GiveUp {
policy: FixedThreshold { threshold: 2 },
}
.wrap(FailingExecutor);
let first = Uuid::from_u128(4);
let second = Uuid::from_u128(5);
assert!(executor.execute(execution_with_id(first)).is_err());
assert!(executor.execute(execution_with_id(second)).is_err());
assert_eq!(
executor.execute(execution_with_id(first)).unwrap(),
Outcome::Breached
);
assert_eq!(
executor.execute(execution_with_id(second)).unwrap(),
Outcome::Breached
);
}
}