use nmbrs_errorhandler::ErrorRouter;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct PolicyConfig {
error_spec: String,
error_rate_max_bits: Option<u64>,
}
impl PolicyConfig {
pub fn new(error_spec: impl Into<String>, error_rate_max: Option<f64>) -> Self {
PolicyConfig {
error_spec: error_spec.into(),
error_rate_max_bits: error_rate_max.map(f64::to_bits),
}
}
pub fn error_spec(&self) -> &str {
&self.error_spec
}
pub fn error_rate_max(&self) -> Option<f64> {
self.error_rate_max_bits.map(f64::from_bits)
}
}
pub struct ErrorPolicy {
config: PolicyConfig,
pub router: ErrorRouter,
derived: Mutex<HashMap<PolicyConfig, Arc<ErrorPolicy>>>,
}
impl ErrorPolicy {
pub fn root(config: PolicyConfig) -> Arc<Self> {
Arc::new(Self::build(config))
}
pub fn standalone(config: PolicyConfig) -> Arc<Self> {
Self::root(config)
}
fn build(config: PolicyConfig) -> Self {
let router =
ErrorRouter::parse(config.error_spec()).unwrap_or_else(|_| ErrorRouter::default_stop());
ErrorPolicy {
config,
router,
derived: Mutex::new(HashMap::new()),
}
}
pub fn resolve_child(self: &Arc<Self>, child: Option<PolicyConfig>) -> Arc<ErrorPolicy> {
match child {
None => self.clone(),
Some(cfg) if cfg == self.config => self.clone(),
Some(cfg) => {
let mut derived = self.derived.lock().unwrap_or_else(|e| e.into_inner());
derived
.entry(cfg.clone())
.or_insert_with(|| Arc::new(ErrorPolicy::build(cfg)))
.clone()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cfg(spec: &str, rate: Option<f64>) -> PolicyConfig {
PolicyConfig::new(spec, rate)
}
#[test]
fn inherits_parent_on_no_override() {
let root = ErrorPolicy::root(cfg(".*:warn,stop", Some(0.1)));
let child = root.resolve_child(None);
assert!(
Arc::ptr_eq(&root, &child),
"no override inherits the parent (depth)"
);
}
#[test]
fn inherits_parent_on_equal_config() {
let root = ErrorPolicy::root(cfg(".*:warn,stop", Some(0.1)));
let child = root.resolve_child(Some(cfg(".*:warn,stop", Some(0.1))));
assert!(
Arc::ptr_eq(&root, &child),
"equal config inherits, no new instance"
);
}
#[test]
fn siblings_with_equal_override_share_one_instance() {
let root = ErrorPolicy::root(cfg(".*:warn,stop", Some(0.1)));
let a = root.resolve_child(Some(cfg("Timeout:retry;.*:stop", Some(0.2))));
let b = root.resolve_child(Some(cfg("Timeout:retry;.*:stop", Some(0.2))));
assert!(
Arc::ptr_eq(&a, &b),
"equal overrides share one derived instance (breadth)"
);
assert!(
!Arc::ptr_eq(&root, &a),
"an override is a new instance, not the parent"
);
}
#[test]
fn distinct_overrides_get_distinct_instances() {
let root = ErrorPolicy::root(cfg(".*:warn,stop", Some(0.1)));
let a = root.resolve_child(Some(cfg(".*:stop", Some(0.2))));
let b = root.resolve_child(Some(cfg(".*:warn", Some(0.3))));
assert!(!Arc::ptr_eq(&a, &b));
}
}