chryso-core 0.0.2

Chryso core crate.
Documentation
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;

const DEFAULT_TENANT: &str = "__default__";

#[derive(Debug, Clone, PartialEq)]
pub enum SystemParamValue {
    Float(f64),
    Int(i64),
    Bool(bool),
    String(String),
}

impl SystemParamValue {
    pub fn as_f64(&self) -> Option<f64> {
        match self {
            SystemParamValue::Float(value) => Some(*value),
            SystemParamValue::Int(value) => Some(*value as f64),
            _ => None,
        }
    }
}

#[derive(Debug, Default)]
pub struct SystemParamRegistry {
    tenants: RwLock<HashMap<String, HashMap<String, SystemParamValue>>>,
}

impl SystemParamRegistry {
    pub fn new() -> Self {
        Self {
            tenants: RwLock::new(HashMap::new()),
        }
    }

    pub fn shared() -> Arc<Self> {
        Arc::new(Self::new())
    }

    pub fn set_param(&self, tenant: &str, key: impl Into<String>, value: SystemParamValue) {
        let mut guard = self.tenants.write();
        let entry = guard.entry(tenant.to_string()).or_default();
        entry.insert(key.into(), value);
    }

    pub fn set_default_param(&self, key: impl Into<String>, value: SystemParamValue) {
        self.set_param(DEFAULT_TENANT, key, value);
    }

    pub fn get_param(&self, tenant: Option<&str>, key: &str) -> Option<SystemParamValue> {
        let guard = self.tenants.read();
        if let Some(tenant) = tenant {
            if let Some(params) = guard.get(tenant) {
                if let Some(value) = params.get(key) {
                    return Some(value.clone());
                }
            }
        }
        guard
            .get(DEFAULT_TENANT)
            .and_then(|params| params.get(key))
            .cloned()
    }

    pub fn get_f64(&self, tenant: Option<&str>, key: &str) -> Option<f64> {
        self.get_param(tenant, key).and_then(|value| value.as_f64())
    }
}

#[cfg(test)]
mod tests {
    use super::{SystemParamRegistry, SystemParamValue};

    #[test]
    fn registry_uses_default_fallback() {
        let registry = SystemParamRegistry::new();
        registry.set_default_param("optimizer.cost.scan", SystemParamValue::Float(2.0));
        assert_eq!(
            registry.get_f64(Some("tenant-a"), "optimizer.cost.scan"),
            Some(2.0)
        );
    }

    #[test]
    fn registry_prefers_tenant_specific_value() {
        let registry = SystemParamRegistry::new();
        registry.set_default_param("optimizer.cost.scan", SystemParamValue::Float(2.0));
        registry.set_param(
            "tenant-a",
            "optimizer.cost.scan",
            SystemParamValue::Float(3.0),
        );
        assert_eq!(
            registry.get_f64(Some("tenant-a"), "optimizer.cost.scan"),
            Some(3.0)
        );
    }
}