use crate::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BehaviorAction {
NoOp,
ModifyConversionRate {
multiplier: f64,
},
DeclineTransaction {
reason: String,
},
IncreaseChurnProbability {
factor: f64,
},
ChangeResponseStatus {
status: u16,
},
ModifyLatency {
adjustment_ms: i64,
},
TriggerChaosRule {
rule_name: String,
},
ModifyResponseBody {
path: String,
value: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ActionEffect {
None,
RateMultiplier {
target: String,
multiplier: f64,
},
Rejection {
reason: String,
},
StatusOverride {
status: u16,
},
LatencyAdjustment {
delta_ms: i64,
},
Chaostrigger {
rule_name: String,
},
BodyPatch {
path: String,
value: String,
},
}
#[derive(Debug, Clone)]
pub struct ActionResult {
pub description: String,
pub effect: ActionEffect,
}
pub struct ActionExecutor;
impl ActionExecutor {
pub fn new() -> Self {
Self
}
pub fn execute_action(&self, action: &BehaviorAction) -> Result<ActionResult> {
match action {
BehaviorAction::NoOp => Ok(ActionResult {
description: "No operation".to_string(),
effect: ActionEffect::None,
}),
BehaviorAction::ModifyConversionRate { multiplier } => Ok(ActionResult {
description: format!("Modified conversion rate by factor {}", multiplier),
effect: ActionEffect::RateMultiplier {
target: "conversion".to_string(),
multiplier: *multiplier,
},
}),
BehaviorAction::DeclineTransaction { reason } => Ok(ActionResult {
description: format!("Declined transaction: {}", reason),
effect: ActionEffect::Rejection {
reason: reason.clone(),
},
}),
BehaviorAction::IncreaseChurnProbability { factor } => Ok(ActionResult {
description: format!("Increased churn probability by factor {}", factor),
effect: ActionEffect::RateMultiplier {
target: "churn".to_string(),
multiplier: *factor,
},
}),
BehaviorAction::ChangeResponseStatus { status } => Ok(ActionResult {
description: format!("Changed response status to {}", status),
effect: ActionEffect::StatusOverride { status: *status },
}),
BehaviorAction::ModifyLatency { adjustment_ms } => Ok(ActionResult {
description: format!("Modified latency by {}ms", adjustment_ms),
effect: ActionEffect::LatencyAdjustment {
delta_ms: *adjustment_ms,
},
}),
BehaviorAction::TriggerChaosRule { rule_name } => Ok(ActionResult {
description: format!("Triggered chaos rule: {}", rule_name),
effect: ActionEffect::Chaostrigger {
rule_name: rule_name.clone(),
},
}),
BehaviorAction::ModifyResponseBody { path, value } => Ok(ActionResult {
description: format!("Modified response body at {} to {}", path, value),
effect: ActionEffect::BodyPatch {
path: path.clone(),
value: value.clone(),
},
}),
}
}
pub fn execute(&self, action: &BehaviorAction) -> Result<String> {
self.execute_action(action).map(|r| r.description)
}
}
impl Default for ActionExecutor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_noop_action() {
let executor = ActionExecutor::new();
let result = executor.execute(&BehaviorAction::NoOp).unwrap();
assert_eq!(result, "No operation");
}
#[test]
fn test_modify_conversion_rate() {
let executor = ActionExecutor::new();
let result = executor
.execute(&BehaviorAction::ModifyConversionRate { multiplier: 0.8 })
.unwrap();
assert!(result.contains("0.8"));
}
#[test]
fn test_decline_transaction() {
let executor = ActionExecutor::new();
let result = executor
.execute(&BehaviorAction::DeclineTransaction {
reason: "fraud_detected".to_string(),
})
.unwrap();
assert!(result.contains("fraud_detected"));
}
#[test]
fn test_execute_action_status_override() {
let executor = ActionExecutor::new();
let result = executor
.execute_action(&BehaviorAction::ChangeResponseStatus { status: 503 })
.unwrap();
assert_eq!(result.effect, ActionEffect::StatusOverride { status: 503 });
}
#[test]
fn test_execute_action_latency_adjustment() {
let executor = ActionExecutor::new();
let result = executor
.execute_action(&BehaviorAction::ModifyLatency { adjustment_ms: -50 })
.unwrap();
assert_eq!(result.effect, ActionEffect::LatencyAdjustment { delta_ms: -50 });
}
#[test]
fn test_execute_action_body_patch() {
let executor = ActionExecutor::new();
let result = executor
.execute_action(&BehaviorAction::ModifyResponseBody {
path: "$.price".to_string(),
value: "99.99".to_string(),
})
.unwrap();
assert_eq!(
result.effect,
ActionEffect::BodyPatch {
path: "$.price".to_string(),
value: "99.99".to_string(),
}
);
}
#[test]
fn test_execute_action_churn_multiplier() {
let executor = ActionExecutor::new();
let result = executor
.execute_action(&BehaviorAction::IncreaseChurnProbability { factor: 2.0 })
.unwrap();
assert_eq!(
result.effect,
ActionEffect::RateMultiplier {
target: "churn".to_string(),
multiplier: 2.0,
}
);
}
#[test]
fn test_execute_action_noop() {
let executor = ActionExecutor::new();
let result = executor.execute_action(&BehaviorAction::NoOp).unwrap();
assert_eq!(result.effect, ActionEffect::None);
}
}