use anyhow::Result;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq)]
pub enum CircuitState {
Closed, Open, HalfOpen, ThrottledOpen, VectorSpecificOpen(AnalysisVector), }
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum AnalysisVector {
StateTransition,
EconomicExploit,
AccessControl,
MathematicalIntegrity,
General,
CustomVector(String), }
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct EndpointId {
pub service: String, pub endpoint: String, }
#[derive(Debug, Clone)]
pub struct CircuitBreakerConfig {
pub failure_threshold: u32,
pub failure_window: Duration,
pub recovery_timeout: Duration,
pub success_threshold: u32,
pub half_open_max_calls: u32,
}
#[derive(Debug, Clone)]
pub struct CircuitStats {
pub state: CircuitState,
pub failure_count: u32,
pub success_count: u32,
pub last_failure_time: Option<Instant>,
pub last_success_time: Option<Instant>,
pub total_requests: u64,
pub total_failures: u64,
pub state_changes: u32,
}
#[derive(Debug)]
struct CircuitBreakerInstance {
config: CircuitBreakerConfig,
stats: CircuitStats,
failure_times: Vec<Instant>,
half_open_calls: u32,
}
pub struct GranularCircuitBreaker {
endpoint_breakers: Arc<RwLock<HashMap<EndpointId, CircuitBreakerInstance>>>,
vector_breakers: Arc<RwLock<HashMap<AnalysisVector, CircuitBreakerInstance>>>,
global_breaker: Arc<RwLock<CircuitBreakerInstance>>,
default_config: CircuitBreakerConfig,
}
impl Default for CircuitBreakerConfig {
fn default() -> Self {
Self {
failure_threshold: 5,
failure_window: Duration::from_secs(5 * 60), recovery_timeout: Duration::from_secs(60), success_threshold: 3,
half_open_max_calls: 3,
}
}
}
impl Default for CircuitStats {
fn default() -> Self {
Self {
state: CircuitState::Closed,
failure_count: 0,
success_count: 0,
last_failure_time: None,
last_success_time: None,
total_requests: 0,
total_failures: 0,
state_changes: 0,
}
}
}
impl CircuitBreakerInstance {
fn new(config: CircuitBreakerConfig) -> Self {
Self {
config,
stats: CircuitStats::default(),
failure_times: Vec::new(),
half_open_calls: 0,
}
}
fn can_execute(&mut self) -> bool {
self.update_state();
match self.stats.state {
CircuitState::Closed => true,
CircuitState::Open => false,
CircuitState::HalfOpen => {
if self.half_open_calls < self.config.half_open_max_calls {
self.half_open_calls += 1;
true
} else {
false
}
}
CircuitState::ThrottledOpen => {
self.half_open_calls < self.config.half_open_max_calls / 2
}
CircuitState::VectorSpecificOpen(_) => {
true
}
}
}
fn on_success(&mut self) {
self.stats.total_requests += 1;
self.stats.success_count += 1;
self.stats.last_success_time = Some(Instant::now());
match self.stats.state {
CircuitState::HalfOpen => {
if self.stats.success_count >= self.config.success_threshold {
self.close_circuit();
}
}
CircuitState::Open => {
self.close_circuit();
}
CircuitState::Closed => {
self.stats.failure_count = 0;
self.failure_times.clear();
}
CircuitState::ThrottledOpen => {
if self.stats.success_count >= self.config.success_threshold / 2 {
self.stats.state = CircuitState::HalfOpen;
}
}
CircuitState::VectorSpecificOpen(_) => {
}
}
}
fn on_failure(&mut self) {
self.stats.total_requests += 1;
self.stats.total_failures += 1;
self.stats.failure_count += 1;
let now = Instant::now();
self.stats.last_failure_time = Some(now);
self.failure_times.push(now);
self.failure_times
.retain(|&time| now.duration_since(time) <= self.config.failure_window);
if self.failure_times.len() >= self.config.failure_threshold as usize {
self.open_circuit();
}
if self.stats.state == CircuitState::HalfOpen {
self.stats.success_count = 0;
}
}
fn update_state(&mut self) {
match self.stats.state {
CircuitState::Open => {
if let Some(last_failure) = self.stats.last_failure_time {
if Instant::now().duration_since(last_failure) >= self.config.recovery_timeout {
self.half_open_circuit();
}
}
}
CircuitState::Closed | CircuitState::HalfOpen => {
let now = Instant::now();
self.failure_times
.retain(|&time| now.duration_since(time) <= self.config.failure_window);
if self.failure_times.is_empty() {
self.stats.failure_count = 0;
}
}
CircuitState::ThrottledOpen => {
if let Some(last_failure) = self.stats.last_failure_time {
if Instant::now().duration_since(last_failure)
>= self.config.recovery_timeout / 2
{
self.half_open_circuit();
}
}
}
CircuitState::VectorSpecificOpen(_) => {
}
}
}
fn open_circuit(&mut self) {
if self.stats.state != CircuitState::Open {
println!("🚫 Circuit breaker OPENED - blocking requests");
self.stats.state = CircuitState::Open;
self.stats.state_changes += 1;
self.stats.success_count = 0;
self.half_open_calls = 0;
}
}
fn half_open_circuit(&mut self) {
if self.stats.state != CircuitState::HalfOpen {
println!("🔄 Circuit breaker HALF-OPEN - testing recovery");
self.stats.state = CircuitState::HalfOpen;
self.stats.state_changes += 1;
self.stats.success_count = 0;
self.half_open_calls = 0;
}
}
fn close_circuit(&mut self) {
if self.stats.state != CircuitState::Closed {
println!("✅ Circuit breaker CLOSED - normal operation");
self.stats.state = CircuitState::Closed;
self.stats.state_changes += 1;
self.stats.failure_count = 0;
self.stats.success_count = 0;
self.failure_times.clear();
self.half_open_calls = 0;
}
}
}
impl GranularCircuitBreaker {
pub fn new() -> Self {
Self {
endpoint_breakers: Arc::new(RwLock::new(HashMap::new())),
vector_breakers: Arc::new(RwLock::new(HashMap::new())),
global_breaker: Arc::new(RwLock::new(CircuitBreakerInstance::new(
CircuitBreakerConfig::default(),
))),
default_config: CircuitBreakerConfig::default(),
}
}
pub fn with_config(config: CircuitBreakerConfig) -> Self {
Self {
endpoint_breakers: Arc::new(RwLock::new(HashMap::new())),
vector_breakers: Arc::new(RwLock::new(HashMap::new())),
global_breaker: Arc::new(RwLock::new(CircuitBreakerInstance::new(config.clone()))),
default_config: config,
}
}
pub fn configure_endpoint(&self, endpoint: EndpointId, config: CircuitBreakerConfig) {
let mut breakers = self.endpoint_breakers.write().unwrap();
breakers.insert(endpoint, CircuitBreakerInstance::new(config));
}
pub fn configure_vector(&self, vector: AnalysisVector, config: CircuitBreakerConfig) {
let mut breakers = self.vector_breakers.write().unwrap();
breakers.insert(vector, CircuitBreakerInstance::new(config));
}
pub fn can_execute_endpoint(&self, endpoint: &EndpointId) -> bool {
if !self.can_execute_global() {
return false;
}
let mut breakers = self.endpoint_breakers.write().unwrap();
let breaker = breakers
.entry(endpoint.clone())
.or_insert_with(|| CircuitBreakerInstance::new(self.default_config.clone()));
breaker.can_execute()
}
pub fn can_execute_vector(&self, vector: &AnalysisVector) -> bool {
if !self.can_execute_global() {
return false;
}
let mut breakers = self.vector_breakers.write().unwrap();
let breaker = breakers
.entry(vector.clone())
.or_insert_with(|| CircuitBreakerInstance::new(self.default_config.clone()));
breaker.can_execute()
}
pub fn can_execute_global(&self) -> bool {
let mut global = self.global_breaker.write().unwrap();
global.can_execute()
}
pub fn on_success_endpoint(&self, endpoint: &EndpointId) {
self.on_success_global();
let mut breakers = self.endpoint_breakers.write().unwrap();
if let Some(breaker) = breakers.get_mut(endpoint) {
breaker.on_success();
}
}
pub fn on_success_vector(&self, vector: &AnalysisVector) {
self.on_success_global();
let mut breakers = self.vector_breakers.write().unwrap();
if let Some(breaker) = breakers.get_mut(vector) {
breaker.on_success();
}
}
pub fn on_success_global(&self) {
let mut global = self.global_breaker.write().unwrap();
global.on_success();
}
pub fn on_failure_endpoint(&self, endpoint: &EndpointId) {
self.on_failure_global();
let mut breakers = self.endpoint_breakers.write().unwrap();
if let Some(breaker) = breakers.get_mut(endpoint) {
breaker.on_failure();
}
}
pub fn on_failure_vector(&self, vector: &AnalysisVector) {
self.on_failure_global();
let mut breakers = self.vector_breakers.write().unwrap();
if let Some(breaker) = breakers.get_mut(vector) {
breaker.on_failure();
}
}
pub fn on_failure_global(&self) {
let mut global = self.global_breaker.write().unwrap();
global.on_failure();
}
pub fn get_endpoint_stats(&self, endpoint: &EndpointId) -> Option<CircuitStats> {
let breakers = self.endpoint_breakers.read().unwrap();
breakers.get(endpoint).map(|b| b.stats.clone())
}
pub fn get_vector_stats(&self, vector: &AnalysisVector) -> Option<CircuitStats> {
let breakers = self.vector_breakers.read().unwrap();
breakers.get(vector).map(|b| b.stats.clone())
}
pub fn get_global_stats(&self) -> CircuitStats {
let global = self.global_breaker.read().unwrap();
global.stats.clone()
}
pub fn get_status_report(&self) -> CircuitBreakerReport {
let global_stats = self.get_global_stats();
let endpoint_stats: HashMap<EndpointId, CircuitStats> = {
let breakers = self.endpoint_breakers.read().unwrap();
breakers
.iter()
.map(|(k, v)| (k.clone(), v.stats.clone()))
.collect()
};
let vector_stats: HashMap<AnalysisVector, CircuitStats> = {
let breakers = self.vector_breakers.read().unwrap();
breakers
.iter()
.map(|(k, v)| (k.clone(), v.stats.clone()))
.collect()
};
CircuitBreakerReport {
global_stats,
endpoint_stats,
vector_stats,
}
}
pub fn reset_all(&self) {
{
let mut global = self.global_breaker.write().unwrap();
*global = CircuitBreakerInstance::new(self.default_config.clone());
}
{
let mut breakers = self.endpoint_breakers.write().unwrap();
for breaker in breakers.values_mut() {
*breaker = CircuitBreakerInstance::new(self.default_config.clone());
}
}
{
let mut breakers = self.vector_breakers.write().unwrap();
for breaker in breakers.values_mut() {
*breaker = CircuitBreakerInstance::new(self.default_config.clone());
}
}
println!("🔄 All circuit breakers have been reset");
}
pub fn set_vector_enabled(&self, vector: AnalysisVector, enabled: bool) {
let mut breakers = self.vector_breakers.write().unwrap();
if let Some(breaker) = breakers.get_mut(&vector) {
if enabled {
breaker.close_circuit();
println!("✅ Enabled circuit breaker for vector: {:?}", vector);
} else {
breaker.stats.state = CircuitState::VectorSpecificOpen(vector.clone());
println!("🚫 Disabled circuit breaker for vector: {:?}", vector);
}
}
}
pub fn is_vector_available(&self, vector: &AnalysisVector) -> bool {
let breakers = self.vector_breakers.read().unwrap();
if let Some(breaker) = breakers.get(vector) {
match &breaker.stats.state {
CircuitState::Closed | CircuitState::HalfOpen => true,
CircuitState::VectorSpecificOpen(disabled_vector) => disabled_vector != vector,
_ => false,
}
} else {
true }
}
pub fn get_vector_health_score(&self, vector: &AnalysisVector) -> f64 {
let breakers = self.vector_breakers.read().unwrap();
if let Some(breaker) = breakers.get(vector) {
if breaker.stats.total_requests == 0 {
return 1.0;
}
let success_rate =
1.0 - (breaker.stats.total_failures as f64 / breaker.stats.total_requests as f64);
success_rate.max(0.0).min(1.0)
} else {
1.0
}
}
}
#[derive(Debug)]
pub struct CircuitBreakerReport {
pub global_stats: CircuitStats,
pub endpoint_stats: HashMap<EndpointId, CircuitStats>,
pub vector_stats: HashMap<AnalysisVector, CircuitStats>,
}
impl CircuitBreakerReport {
pub fn print_summary(&self) {
println!("\n📊 Circuit Breaker Status Report");
println!("================================");
println!("\n🌐 Global Circuit: {:?}", self.global_stats.state);
println!(" Total Requests: {}", self.global_stats.total_requests);
println!(" Total Failures: {}", self.global_stats.total_failures);
println!(" State Changes: {}", self.global_stats.state_changes);
if !self.endpoint_stats.is_empty() {
println!("\n🔗 Endpoint Circuits:");
for (endpoint, stats) in &self.endpoint_stats {
println!(
" {}/{}: {:?} ({}req, {}fail)",
endpoint.service,
endpoint.endpoint,
stats.state,
stats.total_requests,
stats.total_failures
);
}
}
if !self.vector_stats.is_empty() {
println!("\n🧮 Analysis Vector Circuits:");
for (vector, stats) in &self.vector_stats {
println!(
" {:?}: {:?} ({}req, {}fail)",
vector, stats.state, stats.total_requests, stats.total_failures
);
}
}
}
}
impl Default for GranularCircuitBreaker {
fn default() -> Self {
Self::new()
}
}
impl EndpointId {
pub fn new(service: &str, endpoint: &str) -> Self {
Self {
service: service.to_string(),
endpoint: endpoint.to_string(),
}
}
pub fn osvm_ai() -> Self {
Self::new("osvm.ai", "/api/getAnswer")
}
pub fn openai() -> Self {
Self::new("openai", "/v1/chat/completions")
}
pub fn custom(service: &str, endpoint: &str) -> Self {
Self::new(service, endpoint)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_circuit_breaker_states() {
let breaker = GranularCircuitBreaker::new();
let endpoint = EndpointId::osvm_ai();
assert!(breaker.can_execute_endpoint(&endpoint));
for _ in 0..5 {
breaker.on_failure_endpoint(&endpoint);
}
assert!(!breaker.can_execute_endpoint(&endpoint));
let stats = breaker.get_endpoint_stats(&endpoint).unwrap();
assert_eq!(stats.state, CircuitState::Open);
assert_eq!(stats.total_failures, 5);
}
#[test]
fn test_analysis_vector_circuit() {
let breaker = GranularCircuitBreaker::new();
let vector = AnalysisVector::EconomicExploit;
assert!(breaker.can_execute_vector(&vector));
for _ in 0..5 {
breaker.on_failure_vector(&vector);
}
assert!(!breaker.can_execute_vector(&vector));
}
#[test]
fn test_half_open_recovery() {
let mut config = CircuitBreakerConfig::default();
config.recovery_timeout = Duration::from_millis(100);
let breaker = GranularCircuitBreaker::with_config(config);
let endpoint = EndpointId::osvm_ai();
for _ in 0..5 {
breaker.on_failure_endpoint(&endpoint);
}
assert!(!breaker.can_execute_endpoint(&endpoint));
thread::sleep(Duration::from_millis(150));
assert!(breaker.can_execute_endpoint(&endpoint));
for _ in 0..3 {
breaker.on_success_endpoint(&endpoint);
}
let stats = breaker.get_endpoint_stats(&endpoint).unwrap();
assert_eq!(stats.state, CircuitState::Closed);
}
}