use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::Duration;
use arc_swap::ArcSwap;
use super::Config;
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub row_limit: Option<NonZeroU32>,
pub query_timeout: Duration,
pub log_level: String,
#[cfg(feature = "cache")]
pub cache_default_ttl: Duration,
#[cfg(feature = "cache")]
pub cache_schema_ttl: Duration,
#[cfg(feature = "cache")]
pub cache_query_ttl: Duration,
}
impl RuntimeConfig {
#[must_use]
pub fn from_config(config: &Config) -> Self {
Self {
row_limit: config.row_limit,
query_timeout: config.query_timeout,
log_level: config.telemetry.log_level.clone(),
#[cfg(feature = "cache")]
cache_default_ttl: config.cache.ttl.default,
#[cfg(feature = "cache")]
cache_schema_ttl: config.cache.ttl.schema,
#[cfg(feature = "cache")]
cache_query_ttl: config.cache.ttl.query,
}
}
}
#[derive(Debug)]
pub struct RuntimeConfigHolder {
inner: ArcSwap<RuntimeConfig>,
}
impl RuntimeConfigHolder {
#[must_use]
pub fn new(config: RuntimeConfig) -> Self {
Self {
inner: ArcSwap::from_pointee(config),
}
}
#[must_use]
pub fn load(&self) -> Arc<RuntimeConfig> {
self.inner.load_full()
}
pub fn store(&self, config: RuntimeConfig) {
self.inner.store(Arc::new(config));
}
#[must_use]
pub fn row_limit(&self) -> Option<NonZeroU32> {
self.inner.load().row_limit
}
#[must_use]
pub fn query_timeout(&self) -> Duration {
self.inner.load().query_timeout
}
}
#[derive(Debug, Clone)]
pub enum ReloadTrigger {
Signal,
HttpEndpoint {
remote_addr: Option<String>,
},
Manual,
}
impl std::fmt::Display for ReloadTrigger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Signal => write!(f, "SIGHUP"),
Self::HttpEndpoint { remote_addr } => {
if let Some(addr) = remote_addr {
write!(f, "HTTP /admin/reload from {addr}")
} else {
write!(f, "HTTP /admin/reload")
}
}
Self::Manual => write!(f, "manual"),
}
}
}
#[derive(Debug, Clone)]
pub struct ReloadResult {
pub success: bool,
pub error: Option<String>,
pub changed: Vec<String>,
}
impl ReloadResult {
#[must_use]
pub const fn success(changed: Vec<String>) -> Self {
Self {
success: true,
error: None,
changed,
}
}
#[must_use]
pub const fn failure(error: String) -> Self {
Self {
success: false,
error: Some(error),
changed: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_runtime_config() -> RuntimeConfig {
RuntimeConfig {
row_limit: NonZeroU32::new(1000),
query_timeout: Duration::from_secs(30),
log_level: "info".to_string(),
#[cfg(feature = "cache")]
cache_default_ttl: Duration::from_secs(300),
#[cfg(feature = "cache")]
cache_schema_ttl: Duration::from_secs(3600),
#[cfg(feature = "cache")]
cache_query_ttl: Duration::from_secs(60),
}
}
#[test]
fn test_runtime_config_holder_load() {
let config = create_test_runtime_config();
let holder = RuntimeConfigHolder::new(config);
assert_eq!(holder.row_limit(), NonZeroU32::new(1000));
assert_eq!(holder.query_timeout(), Duration::from_secs(30));
}
#[test]
fn test_runtime_config_holder_store() {
let config = create_test_runtime_config();
let holder = RuntimeConfigHolder::new(config);
let new_config = RuntimeConfig {
row_limit: NonZeroU32::new(500),
query_timeout: Duration::from_secs(60),
log_level: "debug".to_string(),
#[cfg(feature = "cache")]
cache_default_ttl: Duration::from_secs(600),
#[cfg(feature = "cache")]
cache_schema_ttl: Duration::from_secs(7200),
#[cfg(feature = "cache")]
cache_query_ttl: Duration::from_secs(120),
};
holder.store(new_config);
assert_eq!(holder.row_limit(), NonZeroU32::new(500));
assert_eq!(holder.query_timeout(), Duration::from_secs(60));
}
#[test]
fn test_runtime_config_holder_load_full() {
let config = create_test_runtime_config();
let holder = RuntimeConfigHolder::new(config);
let loaded = holder.load();
assert_eq!(loaded.log_level, "info");
}
#[test]
fn test_runtime_config_debug() {
let config = create_test_runtime_config();
let debug_str = format!("{config:?}");
assert!(debug_str.contains("RuntimeConfig"));
assert!(debug_str.contains("row_limit"));
}
#[test]
fn test_runtime_config_clone() {
let config = create_test_runtime_config();
let cloned = config.clone();
assert_eq!(cloned.query_timeout, config.query_timeout);
assert_eq!(cloned.log_level, config.log_level);
}
#[test]
fn test_runtime_config_holder_debug() {
let config = create_test_runtime_config();
let holder = RuntimeConfigHolder::new(config);
let debug_str = format!("{holder:?}");
assert!(debug_str.contains("RuntimeConfigHolder"));
}
#[test]
fn test_reload_trigger_display() {
assert_eq!(ReloadTrigger::Signal.to_string(), "SIGHUP");
assert_eq!(ReloadTrigger::Manual.to_string(), "manual");
assert_eq!(
ReloadTrigger::HttpEndpoint {
remote_addr: Some("127.0.0.1".to_string())
}
.to_string(),
"HTTP /admin/reload from 127.0.0.1"
);
}
#[test]
fn test_reload_trigger_display_http_no_addr() {
assert_eq!(
ReloadTrigger::HttpEndpoint { remote_addr: None }.to_string(),
"HTTP /admin/reload"
);
}
#[test]
fn test_reload_trigger_debug() {
let trigger = ReloadTrigger::Signal;
let debug_str = format!("{trigger:?}");
assert!(debug_str.contains("Signal"));
}
#[test]
fn test_reload_trigger_clone() {
let trigger = ReloadTrigger::HttpEndpoint {
remote_addr: Some("127.0.0.1".to_string()),
};
let cloned = trigger.clone();
if let ReloadTrigger::HttpEndpoint { remote_addr } = cloned {
assert_eq!(remote_addr, Some("127.0.0.1".to_string()));
} else {
panic!("Expected HttpEndpoint");
}
}
#[test]
fn test_reload_result_success() {
let result = ReloadResult::success(vec!["row_limit".to_string()]);
assert!(result.success);
assert!(result.error.is_none());
assert_eq!(result.changed, vec!["row_limit"]);
}
#[test]
fn test_reload_result_success_empty() {
let result = ReloadResult::success(vec![]);
assert!(result.success);
assert!(result.error.is_none());
assert!(result.changed.is_empty());
}
#[test]
fn test_reload_result_failure() {
let result = ReloadResult::failure("Invalid config".to_string());
assert!(!result.success);
assert_eq!(result.error, Some("Invalid config".to_string()));
assert!(result.changed.is_empty());
}
#[test]
fn test_reload_result_debug() {
let result = ReloadResult::success(vec!["test".to_string()]);
let debug_str = format!("{result:?}");
assert!(debug_str.contains("ReloadResult"));
assert!(debug_str.contains("success"));
}
#[test]
fn test_reload_result_clone() {
let result = ReloadResult::success(vec!["a".to_string(), "b".to_string()]);
let cloned = result.clone();
assert_eq!(cloned.success, result.success);
assert_eq!(cloned.changed.len(), 2);
}
#[test]
fn test_runtime_config_holder_row_limit_none() {
let config = RuntimeConfig {
row_limit: None,
query_timeout: Duration::from_secs(30),
log_level: "info".to_string(),
#[cfg(feature = "cache")]
cache_default_ttl: Duration::from_secs(300),
#[cfg(feature = "cache")]
cache_schema_ttl: Duration::from_secs(3600),
#[cfg(feature = "cache")]
cache_query_ttl: Duration::from_secs(60),
};
let holder = RuntimeConfigHolder::new(config);
assert!(holder.row_limit().is_none());
}
}