#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FaultPoint {
BeforeInsert,
AfterInsert,
AfterApprovalRequested,
AfterAttemptPersisted,
AfterActionStarted,
AfterActionReturned,
AfterVerificationStarted,
AfterCompensationStarted,
}
#[cfg(feature = "fault-injection")]
pub use injector::{Arming, Fault, FaultInjector};
#[cfg(feature = "fault-injection")]
mod injector {
use std::collections::HashMap;
use std::sync::{Mutex, PoisonError};
use super::FaultPoint;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fault {
Crash,
Abort,
}
#[derive(Debug, Default)]
pub struct FaultInjector {
armed: Mutex<HashMap<FaultPoint, Fault>>,
reached: Mutex<Vec<FaultPoint>>,
}
#[must_use]
pub struct Arming {
injector: FaultInjector,
point: FaultPoint,
}
impl Arming {
pub fn crash(self) -> FaultInjector {
self.arm(Fault::Crash)
}
pub fn abort(self) -> FaultInjector {
self.arm(Fault::Abort)
}
fn arm(self, fault: Fault) -> FaultInjector {
self.injector
.armed
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(self.point, fault);
self.injector
}
}
impl FaultInjector {
pub fn new() -> Self {
Self::default()
}
pub fn at(self, point: FaultPoint) -> Arming {
Arming {
injector: self,
point,
}
}
pub fn reached(&self) -> Vec<FaultPoint> {
self.reached
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
pub(crate) fn reach(&self, point: FaultPoint) {
self.reached
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(point);
let fault = self
.armed
.lock()
.unwrap_or_else(PoisonError::into_inner)
.remove(&point);
match fault {
Some(Fault::Crash) => panic!("fault injected at {point:?}"),
Some(Fault::Abort) => std::process::abort(),
None => {}
}
}
}
}