use crate::backoff::BackoffPolicy;
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::string::String;
#[cfg(any(feature = "std", feature = "alloc"))]
use alloc::vec::Vec;
#[cfg(any(feature = "std", feature = "alloc"))]
#[derive(Debug, Clone, Default)]
pub struct PolicyRegistry {
entries: Vec<(String, BackoffPolicy)>,
}
#[cfg(any(feature = "std", feature = "alloc"))]
impl PolicyRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&mut self,
name: impl Into<String>,
policy: BackoffPolicy,
) -> Option<BackoffPolicy> {
let name = name.into();
if let Some((_, existing)) = self
.entries
.iter_mut()
.find(|(existing_name, _)| *existing_name == name)
{
let previous = *existing;
*existing = policy;
Some(previous)
} else {
self.entries.push((name, policy));
None
}
}
pub fn get(&self, name: &str) -> Option<BackoffPolicy> {
self.entries
.iter()
.find(|(existing_name, _)| existing_name == name)
.map(|(_, policy)| *policy)
}
pub fn remove(&mut self, name: &str) -> Option<BackoffPolicy> {
if let Some(index) = self
.entries
.iter()
.position(|(existing_name, _)| existing_name == name)
{
Some(self.entries.swap_remove(index).1)
} else {
None
}
}
pub fn all(&self) -> Vec<(String, BackoffPolicy)> {
self.entries.to_vec()
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[cfg(feature = "std")]
use std::sync::{OnceLock, RwLock};
#[cfg(feature = "std")]
fn global_registry() -> &'static RwLock<PolicyRegistry> {
static GLOBAL_POLICIES: OnceLock<RwLock<PolicyRegistry>> = OnceLock::new();
GLOBAL_POLICIES.get_or_init(|| RwLock::new(PolicyRegistry::new()))
}
#[cfg(feature = "std")]
pub fn register_global_policy(
name: impl Into<String>,
policy: BackoffPolicy,
) -> Option<BackoffPolicy> {
let mut guard = global_registry()
.write()
.expect("chronomachines global policy registry poisoned");
guard.register(name, policy)
}
#[cfg(feature = "std")]
pub fn get_global_policy(name: &str) -> Option<BackoffPolicy> {
let guard = global_registry()
.read()
.expect("chronomachines global policy registry poisoned");
guard.get(name)
}
#[cfg(feature = "std")]
pub fn remove_global_policy(name: &str) -> Option<BackoffPolicy> {
let mut guard = global_registry()
.write()
.expect("chronomachines global policy registry poisoned");
guard.remove(name)
}
#[cfg(feature = "std")]
pub fn list_global_policies() -> Vec<(String, BackoffPolicy)> {
let guard = global_registry()
.read()
.expect("chronomachines global policy registry poisoned");
guard.all()
}
#[cfg(feature = "std")]
pub fn clear_global_policies() {
let mut guard = global_registry()
.write()
.expect("chronomachines global policy registry poisoned");
guard.clear();
}
#[cfg(all(test, any(feature = "std", feature = "alloc")))]
mod tests {
use super::*;
use crate::backoff::{BackoffPolicy, ExponentialBackoff};
#[test]
fn test_registry_crud() {
let mut registry = PolicyRegistry::new();
assert!(registry.get("missing").is_none());
let policy = BackoffPolicy::from(ExponentialBackoff::new().max_attempts(5));
assert!(registry.register("api", policy).is_none());
assert_eq!(registry.get("api").unwrap().max_attempts(), 5);
let new_policy = BackoffPolicy::from(ExponentialBackoff::new().max_attempts(3));
let replaced = registry.register("api", new_policy);
assert_eq!(replaced.unwrap().max_attempts(), 5);
assert_eq!(registry.get("api").unwrap().max_attempts(), 3);
let removed = registry.remove("api");
assert!(removed.is_some());
assert!(registry.get("api").is_none());
}
#[cfg(feature = "std")]
#[test]
fn test_global_registry_roundtrip() {
clear_global_policies();
assert!(list_global_policies().is_empty());
let policy = BackoffPolicy::from(ExponentialBackoff::new().max_attempts(4));
assert!(register_global_policy("workers", policy).is_none());
let fetched = get_global_policy("workers").unwrap();
assert_eq!(fetched.max_attempts(), 4);
let removed = remove_global_policy("workers").unwrap();
assert_eq!(removed.max_attempts(), 4);
assert!(get_global_policy("workers").is_none());
}
}