use super::blender::ResponseBlender;
use super::config::{ContinuumConfig, TransitionMode};
use super::schedule::TimeSchedule;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
#[derive(Debug, Clone)]
pub struct RealityContinuumEngine {
config: Arc<RwLock<ContinuumConfig>>,
blender: ResponseBlender,
virtual_clock: Option<Arc<crate::time_travel::VirtualClock>>,
manual_overrides: Arc<RwLock<HashMap<String, f64>>>,
}
impl RealityContinuumEngine {
pub fn new(config: ContinuumConfig) -> Self {
let blender = ResponseBlender::new(config.merge_strategy);
Self {
config: Arc::new(RwLock::new(config)),
blender,
virtual_clock: None,
manual_overrides: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_virtual_clock(
config: ContinuumConfig,
virtual_clock: Arc<crate::time_travel::VirtualClock>,
) -> Self {
let blender = ResponseBlender::new(config.merge_strategy);
Self {
config: Arc::new(RwLock::new(config)),
blender,
virtual_clock: Some(virtual_clock),
manual_overrides: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn set_virtual_clock(&mut self, virtual_clock: Arc<crate::time_travel::VirtualClock>) {
self.virtual_clock = Some(virtual_clock);
}
pub async fn get_blend_ratio(&self, path: &str) -> f64 {
{
let overrides = self.manual_overrides.read().await;
if let Some(&ratio) = overrides.get(path) {
debug!("Using manual override for {}: {}", path, ratio);
return ratio;
}
}
let config = self.config.read().await;
for rule in &config.routes {
if rule.matches_path(path) {
if let Some(ref group) = rule.group {
if let Some(&group_ratio) = config.groups.get(group) {
debug!(
"Using group override for {} (group {}): {}",
path, group, group_ratio
);
return group_ratio;
}
}
debug!("Using route rule for {}: {}", path, rule.ratio);
return rule.ratio;
}
}
if config.transition_mode == TransitionMode::TimeBased
|| config.transition_mode == TransitionMode::Scheduled
{
if let Some(ref schedule) = config.time_schedule {
let current_time = self.get_current_time().await;
let ratio = schedule.calculate_ratio(current_time);
debug!("Using time-based ratio for {}: {} (time: {})", path, ratio, current_time);
return ratio;
}
}
debug!("Using default ratio for {}: {}", path, config.default_ratio);
config.default_ratio
}
pub async fn set_blend_ratio(&self, path: &str, ratio: f64) {
let ratio = ratio.clamp(0.0, 1.0);
let mut overrides = self.manual_overrides.write().await;
overrides.insert(path.to_string(), ratio);
info!("Set manual blend ratio for {}: {}", path, ratio);
}
pub async fn remove_blend_ratio(&self, path: &str) {
let mut overrides = self.manual_overrides.write().await;
if overrides.remove(path).is_some() {
info!("Removed manual blend ratio override for {}", path);
}
}
pub async fn set_group_ratio(&self, group: &str, ratio: f64) {
let ratio = ratio.clamp(0.0, 1.0);
let mut config = self.config.write().await;
config.groups.insert(group.to_string(), ratio);
info!("Set group blend ratio for {}: {}", group, ratio);
}
pub async fn update_from_time(&self, _time: DateTime<Utc>) {
debug!("Continuum engine updated from time: {}", _time);
}
pub fn blender(&self) -> &ResponseBlender {
&self.blender
}
pub async fn get_config(&self) -> ContinuumConfig {
self.config.read().await.clone()
}
pub async fn update_config(&self, config: ContinuumConfig) {
let mut current_config = self.config.write().await;
*current_config = config;
info!("Continuum configuration updated");
}
pub async fn is_enabled(&self) -> bool {
self.config.read().await.enabled
}
pub async fn set_enabled(&self, enabled: bool) {
let mut config = self.config.write().await;
config.enabled = enabled;
if enabled {
info!("Reality Continuum enabled");
} else {
info!("Reality Continuum disabled");
}
}
pub async fn get_time_schedule(&self) -> Option<TimeSchedule> {
self.config.read().await.time_schedule.clone()
}
pub async fn set_time_schedule(&self, schedule: TimeSchedule) {
let mut config = self.config.write().await;
config.time_schedule = Some(schedule);
config.transition_mode = TransitionMode::TimeBased;
info!("Time schedule updated");
}
async fn get_current_time(&self) -> DateTime<Utc> {
if let Some(ref clock) = self.virtual_clock {
clock.now()
} else {
Utc::now()
}
}
pub async fn advance_ratio(&self, increment: f64) {
let mut config = self.config.write().await;
let new_ratio = (config.default_ratio + increment).clamp(0.0, 1.0);
config.default_ratio = new_ratio;
info!("Advanced default blend ratio to {}", new_ratio);
}
pub async fn get_manual_overrides(&self) -> HashMap<String, f64> {
self.manual_overrides.read().await.clone()
}
pub async fn clear_manual_overrides(&self) {
let mut overrides = self.manual_overrides.write().await;
overrides.clear();
info!("Cleared all manual blend ratio overrides");
}
}
impl Default for RealityContinuumEngine {
fn default() -> Self {
Self::new(ContinuumConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::super::config::ContinuumRule;
use super::*;
#[tokio::test]
async fn test_get_blend_ratio_default() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
let ratio = engine.get_blend_ratio("/api/test").await;
assert_eq!(ratio, 0.0); }
#[tokio::test]
async fn test_set_get_blend_ratio() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
engine.set_blend_ratio("/api/test", 0.75).await;
let ratio = engine.get_blend_ratio("/api/test").await;
assert_eq!(ratio, 0.75);
}
#[tokio::test]
async fn test_route_rule_matching() {
let mut config = ContinuumConfig::default();
config.routes.push(ContinuumRule::new("/api/users/*".to_string(), 0.5));
let engine = RealityContinuumEngine::new(config);
let ratio = engine.get_blend_ratio("/api/users/123").await;
assert_eq!(ratio, 0.5);
}
#[tokio::test]
async fn test_group_ratio() {
let mut config = ContinuumConfig::default();
config.groups.insert("api-v1".to_string(), 0.3);
config.routes.push(
ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
);
let engine = RealityContinuumEngine::new(config);
let ratio = engine.get_blend_ratio("/api/users/123").await;
assert_eq!(ratio, 0.3);
}
#[tokio::test]
async fn test_time_based_ratio() {
let start = Utc::now();
let end = start + chrono::Duration::days(30);
let schedule = TimeSchedule::new(start, end, 0.0, 1.0);
let mut config = ContinuumConfig {
transition_mode: TransitionMode::TimeBased,
..Default::default()
};
config.time_schedule = Some(schedule);
let engine = RealityContinuumEngine::new(config);
let ratio = engine.get_blend_ratio("/api/test").await;
assert!(ratio < 0.1);
}
#[tokio::test]
async fn test_remove_blend_ratio() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
engine.set_blend_ratio("/api/test", 0.75).await;
assert_eq!(engine.get_blend_ratio("/api/test").await, 0.75);
engine.remove_blend_ratio("/api/test").await;
assert_eq!(engine.get_blend_ratio("/api/test").await, 0.0); }
#[tokio::test]
async fn test_group_ratio_override() {
let mut config = ContinuumConfig::default();
config.groups.insert("api-v1".to_string(), 0.8);
config.routes.push(
ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
);
let engine = RealityContinuumEngine::new(config);
let ratio = engine.get_blend_ratio("/api/users/123").await;
assert_eq!(ratio, 0.8);
}
#[tokio::test]
async fn test_enable_disable() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
assert!(!engine.is_enabled().await);
engine.set_enabled(true).await;
assert!(engine.is_enabled().await);
engine.set_enabled(false).await;
assert!(!engine.is_enabled().await);
}
#[tokio::test]
async fn test_advance_ratio() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
assert_eq!(engine.get_config().await.default_ratio, 0.0);
engine.advance_ratio(0.2).await;
assert_eq!(engine.get_config().await.default_ratio, 0.2);
engine.advance_ratio(0.5).await;
assert_eq!(engine.get_config().await.default_ratio, 0.7);
engine.advance_ratio(0.5).await;
assert_eq!(engine.get_config().await.default_ratio, 1.0);
}
#[tokio::test]
async fn test_clear_manual_overrides() {
let engine = RealityContinuumEngine::new(ContinuumConfig::default());
engine.set_blend_ratio("/api/test1", 0.5).await;
engine.set_blend_ratio("/api/test2", 0.7).await;
let overrides = engine.get_manual_overrides().await;
assert_eq!(overrides.len(), 2);
engine.clear_manual_overrides().await;
let overrides = engine.get_manual_overrides().await;
assert_eq!(overrides.len(), 0);
}
}