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)]
45pub enum Verdict {
46 Continue,
49 Concede,
51}
52
53pub trait Policy<E> {
64 fn decide(&self, attempts: u32, error: &E) -> Verdict;
67}
68
69pub trait Middleware<E> {
73 type Executor: Executor;
75
76 fn wrap(&self, executor: E) -> Self::Executor;
78}
79
80#[derive(Debug, Default, Clone, Copy)]
84pub struct Identity;
85
86impl<E: Executor> Middleware<E> for Identity {
87 type Executor = E;
88
89 fn wrap(&self, executor: E) -> Self::Executor {
90 executor
91 }
92}
93
94#[derive(Debug, Default, Clone, Copy)]
99pub struct Stack<Inner, Outer> {
100 inner: Inner,
101 outer: Outer,
102}
103
104impl<Inner, Outer> Stack<Inner, Outer> {
105 #[must_use]
108 pub const fn new(inner: Inner, outer: Outer) -> Self {
109 Self { inner, outer }
110 }
111}
112
113impl<E, Inner, Outer> Middleware<E> for Stack<Inner, Outer>
114where
115 E: Executor,
116 Inner: Middleware<E>,
117 Outer: Middleware<Inner::Executor>,
118{
119 type Executor = Outer::Executor;
120
121 fn wrap(&self, executor: E) -> Self::Executor {
122 self.outer.wrap(self.inner.wrap(executor))
123 }
124}
125
126#[derive(Debug, Default, Clone, Copy)]
141pub struct Composition<M> {
142 middleware: M,
143}
144
145impl Composition<Identity> {
146 #[must_use]
148 pub const fn new() -> Self {
149 Self {
150 middleware: Identity,
151 }
152 }
153}
154
155impl<M> Composition<M> {
156 #[must_use]
160 pub fn then<N>(self, next: N) -> Composition<Stack<N, M>> {
161 Composition {
162 middleware: Stack::new(next, self.middleware),
163 }
164 }
165}
166
167impl<E, M> Middleware<E> for Composition<M>
168where
169 E: Executor,
170 M: Middleware<E>,
171{
172 type Executor = M::Executor;
173
174 fn wrap(&self, executor: E) -> Self::Executor {
175 self.middleware.wrap(executor)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use std::collections::HashMap;
182
183 use pacta_contract::Uuid;
184
185 use super::*;
186
187 #[derive(Debug)]
188 struct DummyError;
189
190 impl std::fmt::Display for DummyError {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "dummy error")
193 }
194 }
195
196 impl std::error::Error for DummyError {}
197
198 struct DummyExecutor;
199 impl Executor for DummyExecutor {
200 type Error = DummyError;
201 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
202 Ok(Outcome::Fulfilled)
203 }
204 }
205
206 struct IdentityExecutor<E> {
207 inner: E,
208 }
209 impl<E: Executor> Executor for IdentityExecutor<E> {
210 type Error = E::Error;
211 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
212 self.inner.execute(execution)
213 }
214 }
215
216 struct IdentityMiddleware;
217 impl<E: Executor> Middleware<E> for IdentityMiddleware {
218 type Executor = IdentityExecutor<E>;
219 fn wrap(&self, executor: E) -> Self::Executor {
220 IdentityExecutor { inner: executor }
221 }
222 }
223
224 struct BreachExecutor<E> {
225 _inner: E,
226 }
227 impl<E: Executor> Executor for BreachExecutor<E> {
228 type Error = E::Error;
229 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
230 Ok(Outcome::Breached)
231 }
232 }
233
234 struct BreachMiddleware;
235 impl<E: Executor> Middleware<E> for BreachMiddleware {
236 type Executor = BreachExecutor<E>;
237 fn wrap(&self, executor: E) -> Self::Executor {
238 BreachExecutor { _inner: executor }
239 }
240 }
241
242 fn dummy_execution() -> Execution {
243 Execution::new(Pact::new(
244 Default::default(),
245 "dummy_docket".to_string(),
246 "dummy_kind".to_string(),
247 vec![],
248 ))
249 }
250
251 #[test]
252 fn identity_middleware_preserves_fulfilled() {
253 let middleware = IdentityMiddleware;
254 let mut executor = middleware.wrap(DummyExecutor);
255 let outcome = executor.execute(dummy_execution()).unwrap();
256 assert_eq!(outcome, Outcome::Fulfilled);
257 }
258
259 #[test]
260 fn breach_middleware_alters_outcome() {
261 let middleware = BreachMiddleware;
262 let mut executor = middleware.wrap(DummyExecutor);
263 let outcome = executor.execute(dummy_execution()).unwrap();
264 assert_eq!(outcome, Outcome::Breached);
265 }
266
267 #[test]
268 fn stacked_middleware_composes() {
269 let inner = IdentityMiddleware.wrap(DummyExecutor);
274 let mut stacked = IdentityMiddleware.wrap(inner);
275 let outcome = stacked.execute(dummy_execution()).unwrap();
276 assert_eq!(outcome, Outcome::Fulfilled);
277 }
278
279 #[test]
280 fn stacked_middleware_preserves_ordering() {
281 let inner = IdentityMiddleware.wrap(DummyExecutor);
284 let mut stacked = BreachMiddleware.wrap(inner);
285 let outcome = stacked.execute(dummy_execution()).unwrap();
286 assert_eq!(outcome, Outcome::Breached);
287 }
288
289 #[test]
290 fn identity_wrap_returns_the_executor_unchanged() {
291 let mut executor = Identity.wrap(DummyExecutor);
293 assert_eq!(
294 executor.execute(dummy_execution()).unwrap(),
295 Outcome::Fulfilled
296 );
297 }
298
299 #[test]
300 fn stack_is_itself_a_middleware() {
301 let stack = Stack::new(IdentityMiddleware, BreachMiddleware);
304 let mut executor = stack.wrap(DummyExecutor);
305 assert_eq!(
307 executor.execute(dummy_execution()).unwrap(),
308 Outcome::Breached
309 );
310 }
311
312 #[test]
313 fn composition_assembles_and_drives_to_a_settlement() {
314 let composed = Composition::new()
317 .then(IdentityMiddleware)
318 .then(IdentityMiddleware);
319 let mut executor = composed.wrap(DummyExecutor);
320 assert_eq!(
321 executor.execute(dummy_execution()).unwrap(),
322 Outcome::Fulfilled
323 );
324 }
325
326 #[test]
327 fn composition_orders_first_added_outermost() {
328 use std::cell::RefCell;
329 use std::rc::Rc;
330
331 struct RecordingExecutor<E> {
336 inner: E,
337 label: &'static str,
338 log: Rc<RefCell<Vec<String>>>,
339 }
340 impl<E: Executor> Executor for RecordingExecutor<E> {
341 type Error = E::Error;
342 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
343 self.log.borrow_mut().push(format!("{}:enter", self.label));
344 let outcome = self.inner.execute(execution);
345 self.log.borrow_mut().push(format!("{}:exit", self.label));
346 outcome
347 }
348 }
349 struct Recorder {
350 label: &'static str,
351 log: Rc<RefCell<Vec<String>>>,
352 }
353 impl<E: Executor> Middleware<E> for Recorder {
354 type Executor = RecordingExecutor<E>;
355 fn wrap(&self, inner: E) -> Self::Executor {
356 RecordingExecutor {
357 inner,
358 label: self.label,
359 log: Rc::clone(&self.log),
360 }
361 }
362 }
363
364 struct RecordingInner {
366 log: Rc<RefCell<Vec<String>>>,
367 }
368 impl Executor for RecordingInner {
369 type Error = DummyError;
370 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
371 self.log.borrow_mut().push("executor".to_string());
372 Ok(Outcome::Fulfilled)
373 }
374 }
375
376 let log: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));
377 let composed = Composition::new()
378 .then(Recorder {
379 label: "first",
380 log: Rc::clone(&log),
381 })
382 .then(Recorder {
383 label: "second",
384 log: Rc::clone(&log),
385 });
386 let mut executor = composed.wrap(RecordingInner {
387 log: Rc::clone(&log),
388 });
389 executor.execute(dummy_execution()).unwrap();
390
391 assert_eq!(
394 *log.borrow(),
395 vec![
396 "first:enter",
397 "second:enter",
398 "executor",
399 "second:exit",
400 "first:exit",
401 ]
402 );
403 }
404
405 #[derive(Clone, Copy)]
409 struct FixedThreshold {
410 threshold: u32,
411 }
412
413 impl Policy<DummyError> for FixedThreshold {
414 fn decide(&self, attempts: u32, _error: &DummyError) -> Verdict {
415 if attempts >= self.threshold {
416 Verdict::Concede
417 } else {
418 Verdict::Continue
419 }
420 }
421 }
422
423 struct FailingExecutor;
424 impl Executor for FailingExecutor {
425 type Error = DummyError;
426 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
427 Err(DummyError)
428 }
429 }
430
431 struct GiveUpExecutor<Inner, P> {
432 inner: Inner,
433 policy: P,
434 attempts: HashMap<Uuid, u32>,
435 }
436
437 impl<Inner, P> Executor for GiveUpExecutor<Inner, P>
438 where
439 Inner: Executor,
440 P: Policy<Inner::Error>,
441 {
442 type Error = Inner::Error;
443
444 fn execute(&mut self, execution: Execution) -> Result<Outcome, Self::Error> {
445 let id = execution.pact.id;
446 match self.inner.execute(execution) {
447 Ok(outcome) => {
448 self.attempts.remove(&id);
449 Ok(outcome)
450 }
451 Err(error) => {
452 let attempts = self.attempts.entry(id).or_insert(0);
453 *attempts += 1;
454 match self.policy.decide(*attempts, &error) {
455 Verdict::Continue => Err(error),
456 Verdict::Concede => Ok(Outcome::Breached),
457 }
458 }
459 }
460 }
461 }
462
463 struct GiveUp<P> {
464 policy: P,
465 }
466
467 impl<E, P> Middleware<E> for GiveUp<P>
468 where
469 E: Executor,
470 P: Policy<E::Error> + Clone,
471 {
472 type Executor = GiveUpExecutor<E, P>;
473
474 fn wrap(&self, executor: E) -> Self::Executor {
475 GiveUpExecutor {
476 inner: executor,
477 policy: self.policy.clone(),
478 attempts: HashMap::new(),
479 }
480 }
481 }
482
483 fn execution_with_id(id: Uuid) -> Execution {
484 Execution::new(Pact::new(
485 id,
486 "dummy_docket".to_string(),
487 "dummy_kind".to_string(),
488 vec![],
489 ))
490 }
491
492 #[test]
493 fn below_threshold_keeps_propagating_the_error() {
494 let mut executor = GiveUp {
495 policy: FixedThreshold { threshold: 3 },
496 }
497 .wrap(FailingExecutor);
498 let id = Uuid::from_u128(1);
499
500 assert!(executor.execute(execution_with_id(id)).is_err());
501 assert!(executor.execute(execution_with_id(id)).is_err());
502 }
503
504 #[test]
505 fn reaching_threshold_concedes_as_breached() {
506 let mut executor = GiveUp {
507 policy: FixedThreshold { threshold: 3 },
508 }
509 .wrap(FailingExecutor);
510 let id = Uuid::from_u128(2);
511
512 assert!(executor.execute(execution_with_id(id)).is_err());
513 assert!(executor.execute(execution_with_id(id)).is_err());
514 assert_eq!(
515 executor.execute(execution_with_id(id)).unwrap(),
516 Outcome::Breached
517 );
518 }
519
520 #[test]
521 fn a_success_resets_the_failure_count() {
522 struct FlakyThenFulfilling {
523 fail_next: bool,
524 }
525 impl Executor for FlakyThenFulfilling {
526 type Error = DummyError;
527 fn execute(&mut self, _execution: Execution) -> Result<Outcome, Self::Error> {
528 if self.fail_next {
529 Err(DummyError)
530 } else {
531 Ok(Outcome::Fulfilled)
532 }
533 }
534 }
535
536 let mut executor = GiveUp {
537 policy: FixedThreshold { threshold: 2 },
538 }
539 .wrap(FlakyThenFulfilling { fail_next: true });
540 let id = Uuid::from_u128(3);
541
542 assert!(executor.execute(execution_with_id(id)).is_err());
544
545 executor.inner.fail_next = false;
548 assert_eq!(
549 executor.execute(execution_with_id(id)).unwrap(),
550 Outcome::Fulfilled
551 );
552
553 executor.inner.fail_next = true;
555 assert!(executor.execute(execution_with_id(id)).is_err());
556 }
557
558 #[test]
559 fn distinct_pacts_are_tracked_independently() {
560 let mut executor = GiveUp {
561 policy: FixedThreshold { threshold: 2 },
562 }
563 .wrap(FailingExecutor);
564 let first = Uuid::from_u128(4);
565 let second = Uuid::from_u128(5);
566
567 assert!(executor.execute(execution_with_id(first)).is_err());
568 assert!(executor.execute(execution_with_id(second)).is_err());
569
570 assert_eq!(
572 executor.execute(execution_with_id(first)).unwrap(),
573 Outcome::Breached
574 );
575 assert_eq!(
576 executor.execute(execution_with_id(second)).unwrap(),
577 Outcome::Breached
578 );
579 }
580}