mod context;
mod limits;
mod resilience;
#[cfg(test)]
mod resilience_tests;
pub use context::QueryContext;
pub use limits::{
GuardRailViolation, QueryLimits, DEFAULT_CIRCUIT_FAILURE_THRESHOLD,
DEFAULT_CIRCUIT_RECOVERY_SECONDS, DEFAULT_MAX_CARDINALITY, DEFAULT_MAX_DEPTH,
DEFAULT_MEMORY_LIMIT_BYTES, DEFAULT_RATE_LIMIT_QPS,
};
#[allow(unused_imports)] pub(crate) use resilience::CircuitState;
pub use resilience::{CircuitBreaker, RateLimiter};
#[derive(Debug)]
pub struct GuardRails {
limits: parking_lot::RwLock<QueryLimits>,
pub rate_limiter: RateLimiter,
pub circuit_breaker: CircuitBreaker,
}
impl GuardRails {
#[must_use]
pub fn new() -> Self {
Self::with_limits(QueryLimits::default())
}
#[must_use]
pub fn with_limits(limits: QueryLimits) -> Self {
Self {
rate_limiter: RateLimiter::new(limits.rate_limit_qps),
circuit_breaker: CircuitBreaker::new(
limits.circuit_failure_threshold,
limits.circuit_recovery_seconds,
),
limits: parking_lot::RwLock::new(limits),
}
}
#[must_use]
pub fn create_context(&self) -> QueryContext {
QueryContext::new(self.limits.read().clone())
}
#[must_use]
pub fn limits(&self) -> QueryLimits {
self.limits.read().clone()
}
pub fn update_limits(&self, new_limits: &QueryLimits) {
*self.limits.write() = new_limits.clone();
}
pub fn pre_check(&self, client_id: &str) -> Result<(), GuardRailViolation> {
self.circuit_breaker.check()?;
self.rate_limiter.check(client_id)?;
Ok(())
}
}
impl Default for GuardRails {
fn default() -> Self {
Self::new()
}
}