use crate::settings::merge::deep_merge;
use indexmap::IndexMap;
use serde_json::Value;
use std::cell::RefCell;
thread_local! {
static SETTINGS_OVERRIDES: RefCell<Option<IndexMap<String, Value>>> = const { RefCell::new(None) };
}
pub struct SettingsOverride {
values: IndexMap<String, Value>,
}
impl SettingsOverride {
pub fn new() -> Self {
Self {
values: IndexMap::new(),
}
}
pub fn set(self, key: &str, value: impl Into<String>) -> Self {
self.set_value(key, Value::String(value.into()))
}
pub fn set_value(mut self, key: &str, value: Value) -> Self {
let nested = build_nested(key, value);
deep_merge(&mut self.values, nested);
self
}
pub fn activate(self) -> SettingsOverrideGuard {
SETTINGS_OVERRIDES.with(|cell| {
let mut borrow = cell.borrow_mut();
assert!(
borrow.is_none(),
"SettingsOverride is already active on this thread; \
nested activation is not supported"
);
*borrow = Some(self.values);
});
SettingsOverrideGuard { _private: () }
}
}
impl Default for SettingsOverride {
fn default() -> Self {
Self::new()
}
}
pub struct SettingsOverrideGuard {
_private: (),
}
impl Drop for SettingsOverrideGuard {
fn drop(&mut self) {
SETTINGS_OVERRIDES.with(|cell| {
*cell.borrow_mut() = None;
});
}
}
pub(crate) fn current_overrides() -> Option<IndexMap<String, Value>> {
SETTINGS_OVERRIDES.with(|cell| cell.borrow().clone())
}
fn build_nested(key: &str, value: Value) -> IndexMap<String, Value> {
let parts: Vec<&str> = key.split('.').collect();
let mut result = IndexMap::new();
if parts.len() == 1 {
result.insert(parts[0].to_string(), value);
return result;
}
let mut current = value;
for &part in parts[1..].iter().rev() {
let mut map = serde_json::Map::new();
map.insert(part.to_string(), current);
current = Value::Object(map);
}
result.insert(parts[0].to_string(), current);
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::settings::builder::SettingsBuilder;
use crate::settings::sources::DefaultSource;
use rstest::rstest;
#[rstest]
fn test_override_sets_flat_key() {
let _guard = SettingsOverride::new().set("port", "8080").activate();
let settings = SettingsBuilder::new().build().unwrap();
let port: String = settings.get("port").unwrap();
assert_eq!(port, "8080");
}
#[rstest]
fn test_override_sets_nested_key() {
let _guard = SettingsOverride::new()
.set("email.host", "127.0.0.1")
.activate();
let settings = SettingsBuilder::new().build().unwrap();
let email = settings.get_raw("email").unwrap().as_object().unwrap();
assert_eq!(
email.get("host").unwrap(),
&Value::String("127.0.0.1".to_string())
);
}
#[rstest]
fn test_override_multiple_keys() {
let _guard = SettingsOverride::new()
.set("email.host", "127.0.0.1")
.set("email.port", "2525")
.set("debug", "true")
.activate();
let settings = SettingsBuilder::new().build().unwrap();
let email = settings.get_raw("email").unwrap().as_object().unwrap();
assert_eq!(
email.get("host").unwrap(),
&Value::String("127.0.0.1".to_string())
);
assert_eq!(
email.get("port").unwrap(),
&Value::String("2525".to_string())
);
let debug: String = settings.get("debug").unwrap();
assert_eq!(debug, "true");
}
#[rstest]
fn test_override_guard_clears_on_drop() {
{
let _guard = SettingsOverride::new().set("key", "value").activate();
assert!(current_overrides().is_some());
}
assert!(current_overrides().is_none());
}
#[rstest]
fn test_override_wins_over_all_sources() {
let _guard = SettingsOverride::new().set("port", "9999").activate();
let settings = SettingsBuilder::new()
.add_source(DefaultSource::new().with_value("port", Value::Number(8080.into())))
.build()
.unwrap();
let port: String = settings.get("port").unwrap();
assert_eq!(port, "9999");
}
#[rstest]
fn test_nested_merge_preserves_siblings() {
let _guard = SettingsOverride::new()
.set("db.host", "localhost")
.set("db.port", "5433")
.activate();
let settings = SettingsBuilder::new().build().unwrap();
let db = settings.get_raw("db").unwrap().as_object().unwrap();
assert_eq!(
db.get("host").unwrap(),
&Value::String("localhost".to_string())
);
assert_eq!(db.get("port").unwrap(), &Value::String("5433".to_string()));
}
#[rstest]
fn test_set_value_with_typed_value() {
let _guard = SettingsOverride::new()
.set_value("port", Value::Number(2525.into()))
.activate();
let settings = SettingsBuilder::new().build().unwrap();
let port: i64 = settings.get("port").unwrap();
assert_eq!(port, 2525);
}
#[rstest]
fn test_deep_merge_override_into_existing_object() {
let _guard = SettingsOverride::new()
.set("email.host", "override-host")
.activate();
let settings = SettingsBuilder::new()
.add_source(DefaultSource::new().with_value(
"email",
Value::Object(serde_json::Map::from_iter([
(
"host".to_string(),
Value::String("original-host".to_string()),
),
("port".to_string(), Value::Number(1025.into())),
])),
))
.build()
.unwrap();
let email = settings.get_raw("email").unwrap().as_object().unwrap();
assert_eq!(
email.get("host").unwrap(),
&Value::String("override-host".to_string())
);
assert_eq!(email.get("port").unwrap(), &Value::Number(1025.into()));
}
}