use breaker_machines::{CircuitBreaker, Config, EventKind, MemoryStorage, StorageBackend};
use magnus::{Error, Module, Object, RArray, RHash, Ruby, function, method};
use std::sync::Arc;
#[magnus::wrap(class = "BreakerMachinesNative::Storage")]
struct RubyStorage {
inner: Arc<MemoryStorage>,
}
impl RubyStorage {
fn new() -> Self {
Self {
inner: Arc::new(MemoryStorage::new()),
}
}
fn record_success(&self, circuit_name: String, duration: f64) {
self.inner.record_success(&circuit_name, duration);
}
fn record_failure(&self, circuit_name: String, duration: f64) {
self.inner.record_failure(&circuit_name, duration);
}
fn success_count(&self, circuit_name: String, window_seconds: f64) -> usize {
self.inner.success_count(&circuit_name, window_seconds)
}
fn failure_count(&self, circuit_name: String, window_seconds: f64) -> usize {
self.inner.failure_count(&circuit_name, window_seconds)
}
fn clear(&self, circuit_name: String) {
self.inner.clear(&circuit_name);
}
fn clear_all(&self) {
self.inner.clear_all();
}
fn event_log(ruby: &Ruby, storage: &RubyStorage, circuit_name: String, limit: usize) -> RArray {
let events = storage.inner.event_log(&circuit_name, limit);
let array = ruby.ary_new();
for event in events {
let hash = ruby.hash_new();
let type_sym = match event.kind {
EventKind::Success => "success",
EventKind::Failure => "failure",
};
let _ = hash.aset(ruby.to_symbol("type"), type_sym);
let _ = hash.aset(ruby.to_symbol("timestamp"), event.timestamp);
let _ = hash.aset(
ruby.to_symbol("duration_ms"),
(event.duration * 1000.0).round(),
);
let _ = array.push(hash);
}
array
}
}
#[magnus::wrap(class = "BreakerMachinesNative::Circuit")]
struct RubyCircuit {
inner: std::cell::RefCell<CircuitBreaker>,
}
impl RubyCircuit {
fn new(ruby: &Ruby, name: String, config_hash: RHash) -> Result<Self, Error> {
use magnus::TryConvert;
let failure_threshold: usize = config_hash
.get(ruby.to_symbol("failure_threshold"))
.and_then(|v| usize::try_convert(v).ok())
.unwrap_or(5);
let failure_window_secs: f64 = config_hash
.get(ruby.to_symbol("failure_window_secs"))
.and_then(|v| f64::try_convert(v).ok())
.unwrap_or(60.0);
let half_open_timeout_secs: f64 = config_hash
.get(ruby.to_symbol("half_open_timeout_secs"))
.and_then(|v| f64::try_convert(v).ok())
.unwrap_or(30.0);
let success_threshold: usize = config_hash
.get(ruby.to_symbol("success_threshold"))
.and_then(|v| usize::try_convert(v).ok())
.unwrap_or(2);
let jitter_factor: f64 = config_hash
.get(ruby.to_symbol("jitter_factor"))
.and_then(|v| f64::try_convert(v).ok())
.unwrap_or(0.0);
let failure_rate_threshold: Option<f64> = config_hash
.get(ruby.to_symbol("failure_rate_threshold"))
.and_then(|v| f64::try_convert(v).ok());
let minimum_calls: usize = config_hash
.get(ruby.to_symbol("minimum_calls"))
.and_then(|v| usize::try_convert(v).ok())
.unwrap_or(20);
let config = Config {
failure_threshold: Some(failure_threshold),
failure_rate_threshold,
minimum_calls,
failure_window_secs,
half_open_timeout_secs,
success_threshold,
jitter_factor,
};
Ok(Self {
inner: std::cell::RefCell::new(CircuitBreaker::new(name, config)),
})
}
fn record_success(&self, duration: f64) {
self.inner
.borrow_mut()
.record_success_and_maybe_close(duration);
}
fn record_failure(&self, duration: f64) {
self.inner
.borrow_mut()
.record_failure_and_maybe_trip(duration);
}
fn is_open(&self) -> bool {
self.inner.borrow().is_open()
}
fn is_closed(&self) -> bool {
self.inner.borrow().is_closed()
}
fn state_name(&self) -> String {
self.inner.borrow().state_name().to_lowercase()
}
fn reset(&self) {
self.inner.borrow_mut().reset();
}
}
#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
let module = ruby.define_module("BreakerMachinesNative")?;
let storage_class = module.define_class("Storage", ruby.class_object())?;
storage_class.define_singleton_method("new", function!(RubyStorage::new, 0))?;
storage_class.define_method("record_success", method!(RubyStorage::record_success, 2))?;
storage_class.define_method("record_failure", method!(RubyStorage::record_failure, 2))?;
storage_class.define_method("success_count", method!(RubyStorage::success_count, 2))?;
storage_class.define_method("failure_count", method!(RubyStorage::failure_count, 2))?;
storage_class.define_method("clear", method!(RubyStorage::clear, 1))?;
storage_class.define_method("clear_all", method!(RubyStorage::clear_all, 0))?;
storage_class.define_method("event_log", method!(RubyStorage::event_log, 2))?;
let circuit_class = module.define_class("Circuit", ruby.class_object())?;
circuit_class.define_singleton_method("new", function!(RubyCircuit::new, 2))?;
circuit_class.define_method("record_success", method!(RubyCircuit::record_success, 1))?;
circuit_class.define_method("record_failure", method!(RubyCircuit::record_failure, 1))?;
circuit_class.define_method("is_open", method!(RubyCircuit::is_open, 0))?;
circuit_class.define_method("is_closed", method!(RubyCircuit::is_closed, 0))?;
circuit_class.define_method("state_name", method!(RubyCircuit::state_name, 0))?;
circuit_class.define_method("reset", method!(RubyCircuit::reset, 0))?;
Ok(())
}