1#![forbid(unsafe_code)]
4#![warn(missing_docs)]
5
6use pacta_contract::Pact;
7
8pub use pacta_contract::{Outcome, Settlement};
9
10#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct Execution {
18 pub pact: Pact,
20}
21
22impl Execution {
23 #[must_use]
25 pub fn new(pact: Pact) -> Self {
26 Self { pact }
27 }
28}
29
30pub trait Executor {
32 type Error: std::error::Error;
34
35 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error>;
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[must_use]
46pub enum Verdict {
47 Continue,
50 Concede,
52}
53
54pub trait Policy<E> {
65 fn decide(&self, attempts: u32, error: &E) -> Verdict;
68}
69
70pub trait Middleware<E> {
74 type Executor: Executor;
76
77 fn wrap(&self, executor: E) -> Self::Executor;
79}
80
81#[derive(Debug, Default, Clone, Copy)]
85pub struct Identity;
86
87impl<E: Executor> Middleware<E> for Identity {
88 type Executor = E;
89
90 fn wrap(&self, executor: E) -> Self::Executor {
91 executor
92 }
93}
94
95#[derive(Debug, Default, Clone, Copy)]
100pub struct Stack<Inner, Outer> {
101 inner: Inner,
102 outer: Outer,
103}
104
105impl<Inner, Outer> Stack<Inner, Outer> {
106 #[must_use]
109 pub const fn new(inner: Inner, outer: Outer) -> Self {
110 Self { inner, outer }
111 }
112}
113
114impl<E, Inner, Outer> Middleware<E> for Stack<Inner, Outer>
115where
116 E: Executor,
117 Inner: Middleware<E>,
118 Outer: Middleware<Inner::Executor>,
119{
120 type Executor = Outer::Executor;
121
122 fn wrap(&self, executor: E) -> Self::Executor {
123 self.outer.wrap(self.inner.wrap(executor))
124 }
125}
126
127#[derive(Debug, Default, Clone, Copy)]
142pub struct Composition<M> {
143 middleware: M,
144}
145
146impl Composition<Identity> {
147 #[must_use]
149 pub const fn new() -> Self {
150 Self {
151 middleware: Identity,
152 }
153 }
154}
155
156impl<M> Composition<M> {
157 #[must_use]
161 pub fn then<N>(self, next: N) -> Composition<Stack<N, M>> {
162 Composition {
163 middleware: Stack::new(next, self.middleware),
164 }
165 }
166}
167
168impl<E, M> Middleware<E> for Composition<M>
169where
170 E: Executor,
171 M: Middleware<E>,
172{
173 type Executor = M::Executor;
174
175 fn wrap(&self, executor: E) -> Self::Executor {
176 self.middleware.wrap(executor)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use std::collections::HashMap;
183
184 use pacta_contract::Uuid;
185
186 use super::*;
187
188 #[derive(Debug)]
189 struct DummyError;
190
191 impl std::fmt::Display for DummyError {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(f, "dummy error")
194 }
195 }
196
197 impl std::error::Error for DummyError {}
198
199 struct DummyExecutor;
200 impl Executor for DummyExecutor {
201 type Error = DummyError;
202 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
203 Ok(Outcome::Fulfilled)
204 }
205 }
206
207 struct IdentityExecutor<E> {
208 inner: E,
209 }
210 impl<E: Executor> Executor for IdentityExecutor<E> {
211 type Error = E::Error;
212 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
213 self.inner.execute(execution)
214 }
215 }
216
217 struct IdentityMiddleware;
218 impl<E: Executor> Middleware<E> for IdentityMiddleware {
219 type Executor = IdentityExecutor<E>;
220 fn wrap(&self, executor: E) -> Self::Executor {
221 IdentityExecutor { inner: executor }
222 }
223 }
224
225 struct BreachExecutor<E> {
226 _inner: E,
227 }
228 impl<E: Executor> Executor for BreachExecutor<E> {
229 type Error = E::Error;
230 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
231 Ok(Outcome::Breached)
232 }
233 }
234
235 struct BreachMiddleware;
236 impl<E: Executor> Middleware<E> for BreachMiddleware {
237 type Executor = BreachExecutor<E>;
238 fn wrap(&self, executor: E) -> Self::Executor {
239 BreachExecutor { _inner: executor }
240 }
241 }
242
243 fn dummy_execution() -> Execution {
244 Execution::new(Pact::new(
245 Default::default(),
246 "dummy_docket".to_string(),
247 "dummy_kind".to_string(),
248 vec![],
249 ))
250 }
251
252 #[test]
253 fn identity_middleware_preserves_fulfilled() {
254 let middleware = IdentityMiddleware;
255 let mut executor = middleware.wrap(DummyExecutor);
256 let outcome = executor.execute(dummy_execution()).unwrap();
257 assert_eq!(outcome, Outcome::Fulfilled);
258 }
259
260 #[test]
261 fn breach_middleware_alters_outcome() {
262 let middleware = BreachMiddleware;
263 let mut executor = middleware.wrap(DummyExecutor);
264 let outcome = executor.execute(dummy_execution()).unwrap();
265 assert_eq!(outcome, Outcome::Breached);
266 }
267
268 #[test]
269 fn stacked_middleware_composes() {
270 let inner = IdentityMiddleware.wrap(DummyExecutor);
275 let mut stacked = IdentityMiddleware.wrap(inner);
276 let outcome = stacked.execute(dummy_execution()).unwrap();
277 assert_eq!(outcome, Outcome::Fulfilled);
278 }
279
280 #[test]
281 fn stacked_middleware_preserves_ordering() {
282 let inner = IdentityMiddleware.wrap(DummyExecutor);
285 let mut stacked = BreachMiddleware.wrap(inner);
286 let outcome = stacked.execute(dummy_execution()).unwrap();
287 assert_eq!(outcome, Outcome::Breached);
288 }
289
290 #[test]
291 fn identity_wrap_returns_the_executor_unchanged() {
292 let mut executor = Identity.wrap(DummyExecutor);
294 assert_eq!(
295 executor.execute(dummy_execution()).unwrap(),
296 Outcome::Fulfilled
297 );
298 }
299
300 #[test]
301 fn stack_is_itself_a_middleware() {
302 let stack = Stack::new(IdentityMiddleware, BreachMiddleware);
305 let mut executor = stack.wrap(DummyExecutor);
306 assert_eq!(
308 executor.execute(dummy_execution()).unwrap(),
309 Outcome::Breached
310 );
311 }
312
313 #[test]
314 fn composition_assembles_and_drives_to_a_settlement() {
315 let composed = Composition::new()
318 .then(IdentityMiddleware)
319 .then(IdentityMiddleware);
320 let mut executor = composed.wrap(DummyExecutor);
321 assert_eq!(
322 executor.execute(dummy_execution()).unwrap(),
323 Outcome::Fulfilled
324 );
325 }
326
327 #[test]
328 fn composition_orders_first_added_outermost() {
329 use std::cell::RefCell;
330 use std::rc::Rc;
331
332 struct RecordingExecutor<E> {
337 inner: E,
338 label: &'static str,
339 log: Rc<RefCell<Vec<String>>>,
340 }
341 impl<E: Executor> Executor for RecordingExecutor<E> {
342 type Error = E::Error;
343 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
344 self.log.borrow_mut().push(format!("{}:enter", self.label));
345 let outcome = self.inner.execute(execution);
346 self.log.borrow_mut().push(format!("{}:exit", self.label));
347 outcome
348 }
349 }
350 struct Recorder {
351 label: &'static str,
352 log: Rc<RefCell<Vec<String>>>,
353 }
354 impl<E: Executor> Middleware<E> for Recorder {
355 type Executor = RecordingExecutor<E>;
356 fn wrap(&self, inner: E) -> Self::Executor {
357 RecordingExecutor {
358 inner,
359 label: self.label,
360 log: Rc::clone(&self.log),
361 }
362 }
363 }
364
365 struct RecordingInner {
367 log: Rc<RefCell<Vec<String>>>,
368 }
369 impl Executor for RecordingInner {
370 type Error = DummyError;
371 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
372 self.log.borrow_mut().push("executor".to_string());
373 Ok(Outcome::Fulfilled)
374 }
375 }
376
377 let log: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
378 let composed = Composition::new()
379 .then(Recorder {
380 label: "first",
381 log: Rc::clone(&log),
382 })
383 .then(Recorder {
384 label: "second",
385 log: Rc::clone(&log),
386 });
387 let mut executor = composed.wrap(RecordingInner {
388 log: Rc::clone(&log),
389 });
390 let _ = executor.execute(dummy_execution()).unwrap();
391
392 assert_eq!(
395 *log.borrow(),
396 vec![
397 "first:enter",
398 "second:enter",
399 "executor",
400 "second:exit",
401 "first:exit",
402 ]
403 );
404 }
405
406 #[derive(Clone, Copy)]
410 struct FixedThreshold {
411 threshold: u32,
412 }
413
414 impl Policy<DummyError> for FixedThreshold {
415 fn decide(&self, attempts: u32, _error: &DummyError) -> Verdict {
416 if attempts >= self.threshold {
417 Verdict::Concede
418 } else {
419 Verdict::Continue
420 }
421 }
422 }
423
424 struct FailingExecutor;
425 impl Executor for FailingExecutor {
426 type Error = DummyError;
427 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
428 Err(DummyError)
429 }
430 }
431
432 struct GiveUpExecutor<Inner, P> {
433 inner: Inner,
434 policy: P,
435 attempts: HashMap<Uuid, u32>,
436 }
437
438 impl<Inner, P> Executor for GiveUpExecutor<Inner, P>
439 where
440 Inner: Executor,
441 P: Policy<Inner::Error>,
442 {
443 type Error = Inner::Error;
444
445 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
446 let id = execution.pact.id;
447 match self.inner.execute(execution) {
448 Ok(outcome) => {
449 self.attempts.remove(&id);
450 Ok(outcome)
451 }
452 Err(error) => {
453 let attempts = self.attempts.entry(id).or_insert(0);
454 *attempts += 1;
455 match self.policy.decide(*attempts, &error) {
456 Verdict::Continue => Err(error),
457 Verdict::Concede => Ok(Outcome::Breached),
458 }
459 }
460 }
461 }
462 }
463
464 struct GiveUp<P> {
465 policy: P,
466 }
467
468 impl<E, P> Middleware<E> for GiveUp<P>
469 where
470 E: Executor,
471 P: Policy<E::Error> + Clone,
472 {
473 type Executor = GiveUpExecutor<E, P>;
474
475 fn wrap(&self, executor: E) -> Self::Executor {
476 GiveUpExecutor {
477 inner: executor,
478 policy: self.policy.clone(),
479 attempts: HashMap::new(),
480 }
481 }
482 }
483
484 fn execution_with_id(id: Uuid) -> Execution {
485 Execution::new(Pact::new(
486 id,
487 "dummy_docket".to_string(),
488 "dummy_kind".to_string(),
489 vec![],
490 ))
491 }
492
493 #[test]
494 fn below_threshold_keeps_propagating_the_error() {
495 let mut executor = GiveUp {
496 policy: FixedThreshold { threshold: 3 },
497 }
498 .wrap(FailingExecutor);
499 let id = Uuid::from_u128(1);
500
501 assert!(executor.execute(execution_with_id(id)).is_err());
502 assert!(executor.execute(execution_with_id(id)).is_err());
503 }
504
505 #[test]
506 fn reaching_threshold_concedes_as_breached() {
507 let mut executor = GiveUp {
508 policy: FixedThreshold { threshold: 3 },
509 }
510 .wrap(FailingExecutor);
511 let id = Uuid::from_u128(2);
512
513 assert!(executor.execute(execution_with_id(id)).is_err());
514 assert!(executor.execute(execution_with_id(id)).is_err());
515 assert_eq!(
516 executor.execute(execution_with_id(id)).unwrap(),
517 Outcome::Breached
518 );
519 }
520
521 struct TerminalReviewExecutor<Inner, P> {
528 inner: Inner,
529 policy: P,
530 attempts: HashMap<Uuid, u32>,
531 tribunal: std::rc::Rc<std::cell::RefCell<Vec<Uuid>>>,
532 }
533
534 impl<Inner, P> Executor for TerminalReviewExecutor<Inner, P>
535 where
536 Inner: Executor,
537 P: Policy<Inner::Error>,
538 {
539 type Error = Inner::Error;
540
541 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
542 let id = execution.pact.id;
543 match self.inner.execute(execution) {
544 Ok(outcome) => {
545 self.attempts.remove(&id);
546 Ok(outcome)
547 }
548 Err(error) => {
549 let attempts = self.attempts.entry(id).or_insert(0);
550 *attempts += 1;
551 match self.policy.decide(*attempts, &error) {
552 Verdict::Continue => Err(error),
553 Verdict::Concede => {
554 self.tribunal.borrow_mut().push(id);
558 Ok(Outcome::Breached)
559 }
560 }
561 }
562 }
563 }
564 }
565
566 #[test]
567 fn exhausted_pact_is_recorded_for_operator_review() {
568 use std::cell::RefCell;
569 use std::rc::Rc;
570
571 let tribunal: Rc<RefCell<Vec<Uuid>>> = Rc::new(RefCell::new(Vec::new()));
572 let id = Uuid::from_u128(100);
573 let mut executor = TerminalReviewExecutor {
574 inner: FailingExecutor,
575 policy: FixedThreshold { threshold: 3 },
576 attempts: HashMap::new(),
577 tribunal: Rc::clone(&tribunal),
578 };
579
580 assert!(executor.execute(execution_with_id(id)).is_err());
581 assert!(
582 tribunal.borrow().is_empty(),
583 "not yet exhausted; nothing recorded"
584 );
585 assert!(executor.execute(execution_with_id(id)).is_err());
586 assert!(
587 tribunal.borrow().is_empty(),
588 "still not yet exhausted; nothing recorded"
589 );
590 assert_eq!(
591 executor.execute(execution_with_id(id)).unwrap(),
592 Outcome::Breached
593 );
594 assert_eq!(
595 *tribunal.borrow(),
596 vec![id],
597 "the exhausted pact should be recorded for operator review exactly once"
598 );
599 }
600
601 #[test]
602 fn a_success_resets_the_failure_count() {
603 struct FlakyThenFulfilling {
604 fail_next: bool,
605 }
606 impl Executor for FlakyThenFulfilling {
607 type Error = DummyError;
608 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
609 if self.fail_next {
610 Err(DummyError)
611 } else {
612 Ok(Outcome::Fulfilled)
613 }
614 }
615 }
616
617 let mut executor = GiveUp {
618 policy: FixedThreshold { threshold: 2 },
619 }
620 .wrap(FlakyThenFulfilling { fail_next: true });
621 let id = Uuid::from_u128(3);
622
623 assert!(executor.execute(execution_with_id(id)).is_err());
625
626 executor.inner.fail_next = false;
629 assert_eq!(
630 executor.execute(execution_with_id(id)).unwrap(),
631 Outcome::Fulfilled
632 );
633
634 executor.inner.fail_next = true;
636 assert!(executor.execute(execution_with_id(id)).is_err());
637 }
638
639 #[test]
640 fn distinct_pacts_are_tracked_independently() {
641 let mut executor = GiveUp {
642 policy: FixedThreshold { threshold: 2 },
643 }
644 .wrap(FailingExecutor);
645 let first = Uuid::from_u128(4);
646 let second = Uuid::from_u128(5);
647
648 assert!(executor.execute(execution_with_id(first)).is_err());
649 assert!(executor.execute(execution_with_id(second)).is_err());
650
651 assert_eq!(
653 executor.execute(execution_with_id(first)).unwrap(),
654 Outcome::Breached
655 );
656 assert_eq!(
657 executor.execute(execution_with_id(second)).unwrap(),
658 Outcome::Breached
659 );
660 }
661}