etdl-core 0.1.1

ETDL runtime: BranchMonitor, retry policies, SLA anomaly detection, chaos injection, and telemetry for reliability-aware event-driven services
Documentation
use std::collections::HashSet;

pub struct ChaosController {
    enabled: bool,
    seed: Option<u64>,
    scope: Option<HashSet<String>>,
    production_detected: bool,
    counter: u64,
}

impl ChaosController {
    pub fn new() -> Self {
        let production = ChaosController::detect_production();

        ChaosController {
            enabled: !production && ChaosController::env_chaos_enabled(),
            seed: ChaosController::env_chaos_seed(),
            scope: ChaosController::env_chaos_scope(),
            production_detected: production,
            counter: 0,
        }
    }

    pub fn should_inject_chaos(&mut self, node_id: &str) -> bool {
        if !self.enabled || self.production_detected {
            return false;
        }

        if let Some(ref scope) = self.scope {
            if !scope.contains(node_id) && !scope.contains("*") {
                return false;
            }
        }

        self.counter += 1;

        if let Some(seed) = self.seed {
            let hash = simple_hash(seed, self.counter, node_id);
            hash % 2 == 0
        } else {
            self.counter % 2 == 0
        }
    }

    fn detect_production() -> bool {
        let env = std::env::var("ETDL_ENV")
            .or_else(|_| std::env::var("DEPLOYMENT_ENVIRONMENT"))
            .or_else(|_| std::env::var("ENV"))
            .unwrap_or_default();

        matches!(
            env.to_lowercase().as_str(),
            "production" | "prod" | "prd" | "live"
        )
    }

    fn env_chaos_enabled() -> bool {
        std::env::var("ETDL_CHAOS")
            .map(|v| matches!(v.to_lowercase().as_str(), "true" | "1" | "yes" | "on"))
            .unwrap_or(false)
    }

    fn env_chaos_seed() -> Option<u64> {
        std::env::var("ETDL_CHAOS_SEED")
            .ok()
            .and_then(|v| v.parse().ok())
    }

    fn env_chaos_scope() -> Option<HashSet<String>> {
        std::env::var("ETDL_CHAOS_SCOPE")
            .ok()
            .map(|v| v.split(',').map(|s| s.trim().to_string()).collect())
    }
}

fn simple_hash(seed: u64, counter: u64, node_id: &str) -> u64 {
    let mut hash = seed.wrapping_mul(0x9E3779B9);
    hash = hash.wrapping_add(counter);
    for byte in node_id.bytes() {
        hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
    }
    hash
}

impl Default for ChaosController {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_chaos_disabled_in_production() {
        std::env::set_var("ETDL_ENV", "production");
        std::env::set_var("ETDL_CHAOS", "true");

        let mut controller = ChaosController::new();
        assert!(!controller.should_inject_chaos("test_node"));

        std::env::remove_var("ETDL_ENV");
        std::env::remove_var("ETDL_CHAOS");
    }

    #[test]
    fn test_chaos_scope_filtering() {
        std::env::set_var("ETDL_CHAOS", "true");
        std::env::set_var("ETDL_CHAOS_SCOPE", "node_a,node_b");

        let mut controller = ChaosController::new();

        assert!(controller.scope.as_ref().unwrap().contains("node_a"));
        assert!(!controller.scope.as_ref().unwrap().contains("node_c"));

        std::env::remove_var("ETDL_CHAOS");
        std::env::remove_var("ETDL_CHAOS_SCOPE");
    }

    #[test]
    fn test_chaos_deterministic_with_seed() {
        std::env::set_var("ETDL_CHAOS", "true");
        std::env::set_var("ETDL_CHAOS_SEED", "42");

        let mut c1 = ChaosController::new();
        let mut c2 = ChaosController::new();

        let r1 = c1.should_inject_chaos("test");
        let r2 = c2.should_inject_chaos("test");
        assert_eq!(r1, r2);

        std::env::remove_var("ETDL_CHAOS");
        std::env::remove_var("ETDL_CHAOS_SEED");
    }
}