use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use azure_core::http::StatusCode;
use super::condition::FaultInjectionCondition;
use super::result::FaultInjectionResult;
#[derive(Debug)]
pub struct FaultInjectionRule {
pub condition: FaultInjectionCondition,
pub result: FaultInjectionResult,
pub start_time: Instant,
pub end_time: Option<Instant>,
pub hit_limit: Option<u32>,
pub id: String,
enabled: Arc<AtomicBool>,
hit_count: Arc<AtomicU32>,
passthrough_statuses: Mutex<Vec<StatusCode>>,
}
impl Clone for FaultInjectionRule {
fn clone(&self) -> Self {
Self {
condition: self.condition.clone(),
result: self.result.clone(),
start_time: self.start_time,
end_time: self.end_time,
hit_limit: self.hit_limit,
id: self.id.clone(),
enabled: Arc::new(AtomicBool::new(self.enabled.load(Ordering::SeqCst))),
hit_count: Arc::new(AtomicU32::new(self.hit_count.load(Ordering::SeqCst))),
passthrough_statuses: Mutex::new(self.passthrough_statuses.lock().unwrap().clone()),
}
}
}
impl FaultInjectionRule {
pub fn is_enabled(&self) -> bool {
self.enabled.load(Ordering::SeqCst)
}
pub fn enable(&self) {
self.enabled.store(true, Ordering::SeqCst);
}
pub fn disable(&self) {
self.enabled.store(false, Ordering::SeqCst);
}
pub fn hit_count(&self) -> u32 {
self.hit_count.load(Ordering::SeqCst)
}
pub(super) fn increment_hit_count(&self) {
self.hit_count.fetch_add(1, Ordering::SeqCst);
}
pub fn reset_hit_count(&self) {
self.hit_count.store(0, Ordering::SeqCst);
}
pub(crate) fn shared_enabled(&self) -> Arc<AtomicBool> {
Arc::clone(&self.enabled)
}
pub(crate) fn shared_hit_count(&self) -> Arc<AtomicU32> {
Arc::clone(&self.hit_count)
}
pub(super) fn record_passthrough_status(&self, status: StatusCode) {
self.passthrough_statuses.lock().unwrap().push(status);
}
pub fn passthrough_statuses(&self) -> Vec<StatusCode> {
self.passthrough_statuses.lock().unwrap().clone()
}
}
pub struct FaultInjectionRuleBuilder {
condition: FaultInjectionCondition,
result: FaultInjectionResult,
start_time: Instant,
end_time: Option<Instant>,
hit_limit: Option<u32>,
id: String,
}
impl FaultInjectionRuleBuilder {
pub fn new(id: impl Into<String>, result: FaultInjectionResult) -> Self {
Self {
condition: FaultInjectionCondition::default(),
result,
start_time: Instant::now(),
end_time: None,
hit_limit: None,
id: id.into(),
}
}
pub fn with_condition(mut self, condition: FaultInjectionCondition) -> Self {
self.condition = condition;
self
}
pub fn with_result(mut self, result: FaultInjectionResult) -> Self {
self.result = result;
self
}
pub fn with_start_time(mut self, start_time: Instant) -> Self {
self.start_time = start_time;
self
}
pub fn with_end_time(mut self, end_time: Instant) -> Self {
self.end_time = Some(end_time);
self
}
pub fn with_hit_limit(mut self, hit_limit: u32) -> Self {
self.hit_limit = Some(hit_limit);
self
}
pub fn build(self) -> FaultInjectionRule {
FaultInjectionRule {
condition: self.condition,
result: self.result,
start_time: self.start_time,
end_time: self.end_time,
hit_limit: self.hit_limit,
id: self.id,
enabled: Arc::new(AtomicBool::new(true)),
hit_count: Arc::new(AtomicU32::new(0)),
passthrough_statuses: Mutex::new(Vec::new()),
}
}
}
#[cfg(test)]
mod tests {
use super::FaultInjectionRuleBuilder;
use crate::fault_injection::{FaultInjectionErrorType, FaultInjectionResultBuilder};
use std::time::Instant;
fn create_test_error() -> crate::fault_injection::FaultInjectionResult {
FaultInjectionResultBuilder::new()
.with_error(FaultInjectionErrorType::Timeout)
.build()
}
#[test]
fn builder_default_values() {
let before = Instant::now();
let rule = FaultInjectionRuleBuilder::new("test-rule", create_test_error()).build();
assert_eq!(rule.id, "test-rule");
assert!(rule.start_time >= before);
assert!(rule.start_time <= Instant::now());
assert!(rule.end_time.is_none());
assert!(rule.hit_limit.is_none());
assert!(rule.condition.operation_type.is_none());
assert!(rule.is_enabled());
assert_eq!(rule.hit_count(), 0);
}
#[test]
fn hit_count_increments() {
let rule = FaultInjectionRuleBuilder::new("hit-test", create_test_error()).build();
assert_eq!(rule.hit_count(), 0);
rule.increment_hit_count();
assert_eq!(rule.hit_count(), 1);
rule.increment_hit_count();
rule.increment_hit_count();
assert_eq!(rule.hit_count(), 3);
}
#[test]
fn reset_hit_count_clears_counter() {
let rule = FaultInjectionRuleBuilder::new("reset-test", create_test_error()).build();
rule.increment_hit_count();
rule.increment_hit_count();
assert_eq!(rule.hit_count(), 2);
rule.reset_hit_count();
assert_eq!(rule.hit_count(), 0);
}
}