use std::cell::RefCell;
use std::collections::HashMap;
use serde_json::Value;
use crate::{GLOBAL_OPTIONS, Result};
thread_local! {
static OVERRIDES: RefCell<HashMap<String, HashMap<String, Value>>> = RefCell::new(HashMap::new());
}
pub fn set_override(namespace: &str, key: &str, value: Value) {
OVERRIDES.with(|o| {
o.borrow_mut()
.entry(namespace.to_string())
.or_default()
.insert(key.to_string(), value);
});
}
pub fn get_override(namespace: &str, key: &str) -> Option<Value> {
OVERRIDES.with(|o| {
o.borrow()
.get(namespace)
.and_then(|ns| ns.get(key).cloned())
})
}
pub fn clear_override(namespace: &str, key: &str) {
OVERRIDES.with(|o| {
if let Some(ns_map) = o.borrow_mut().get_mut(namespace) {
ns_map.remove(key);
}
});
}
pub struct OverrideGuard {
previous: Vec<(String, String, Option<Value>)>,
}
impl Drop for OverrideGuard {
fn drop(&mut self) {
OVERRIDES.with(|o| {
let mut map = o.borrow_mut();
for (ns, key, prev_value) in self.previous.drain(..) {
match prev_value {
Some(v) => {
map.entry(ns).or_default().insert(key, v);
}
None => {
if let Some(ns_map) = map.get_mut(&ns) {
ns_map.remove(&key);
}
}
}
}
});
}
}
pub fn override_options(overrides: &[(&str, &str, Value)]) -> Result<OverrideGuard> {
let opts = GLOBAL_OPTIONS
.get()
.ok_or(crate::OptionsError::NotInitialized)?;
for (ns, key, value) in overrides {
opts.validate_override(ns, key, value)?;
}
let mut previous = Vec::with_capacity(overrides.len());
OVERRIDES.with(|o| {
let mut map = o.borrow_mut();
for (ns, key, value) in overrides {
let prev = map.get(*ns).and_then(|m| m.get(*key).cloned());
previous.push((ns.to_string(), key.to_string(), prev));
map.entry(ns.to_string())
.or_default()
.insert(key.to_string(), value.clone());
}
});
Ok(OverrideGuard { previous })
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_set_get_clear_override() {
set_override("ns", "key", json!(true));
assert_eq!(get_override("ns", "key"), Some(json!(true)));
clear_override("ns", "key");
assert_eq!(get_override("ns", "key"), None);
}
#[test]
fn test_override_guard_restores() {
crate::init().unwrap();
set_override("sentry-options-testing", "int-option", json!(1));
{
let _guard =
override_options(&[("sentry-options-testing", "int-option", json!(2))]).unwrap();
assert_eq!(
get_override("sentry-options-testing", "int-option"),
Some(json!(2))
);
}
assert_eq!(
get_override("sentry-options-testing", "int-option"),
Some(json!(1))
);
clear_override("sentry-options-testing", "int-option");
}
#[test]
fn test_override_guard_clears_new_key() {
crate::init().unwrap();
assert_eq!(get_override("sentry-options-testing", "bool-option"), None);
{
let _guard =
override_options(&[("sentry-options-testing", "bool-option", json!(true))])
.unwrap();
assert_eq!(
get_override("sentry-options-testing", "bool-option"),
Some(json!(true))
);
}
assert_eq!(get_override("sentry-options-testing", "bool-option"), None);
}
#[test]
fn test_nested_overrides() {
crate::init().unwrap();
{
let _outer =
override_options(&[("sentry-options-testing", "int-option", json!(100))]).unwrap();
assert_eq!(
get_override("sentry-options-testing", "int-option"),
Some(json!(100))
);
{
let _inner =
override_options(&[("sentry-options-testing", "int-option", json!(200))])
.unwrap();
assert_eq!(
get_override("sentry-options-testing", "int-option"),
Some(json!(200))
);
}
assert_eq!(
get_override("sentry-options-testing", "int-option"),
Some(json!(100))
);
}
assert_eq!(get_override("sentry-options-testing", "int-option"), None);
}
}