use std::cell::RefCell;
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, PoisonError, RwLock};
use arc_swap::ArcSwap;
use crate::{BoxError, Category, Config, Error, Provenance, Source, SourceArc};
thread_local! {
static PREVIEWS: RefCell<Vec<(usize, SourceArc)>> = const { RefCell::new(Vec::new()) };
}
struct PreviewGuard;
impl Drop for PreviewGuard {
fn drop(&mut self) {
PREVIEWS.with(|previews| previews.borrow_mut().pop());
}
}
#[derive(Clone)]
pub struct DynamicSource {
name: String,
current: Arc<RwLock<SourceArc>>,
}
impl DynamicSource {
pub fn new<S: Source + Send + Sync + 'static>(name: impl Into<String>, source: S) -> Self {
Self {
name: name.into(),
current: Arc::new(RwLock::new(Arc::new(source))),
}
}
pub fn replace<S: Source + Send + Sync + 'static>(&self, source: S) {
*self.current.write().unwrap_or_else(PoisonError::into_inner) = Arc::new(source);
}
fn slot(&self) -> usize {
Arc::as_ptr(&self.current).addr()
}
fn inner(&self) -> SourceArc {
let slot = self.slot();
let preview = PREVIEWS.with(|previews| {
let previews = previews.borrow();
previews
.iter()
.rev()
.find(|(key, _)| *key == slot)
.map(|(_, source)| source.clone())
});
if let Some(source) = preview {
return source;
}
self.current
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
}
impl Source for DynamicSource {
fn name(&self) -> &str {
&self.name
}
fn category(&self) -> Category {
self.inner().category()
}
fn deserialize(
&self,
seed: &mut dyn for<'de> FnMut(
&mut dyn erased_serde::Deserializer<'de>,
) -> Result<(), erased_serde::Error>,
) -> Result<(), erased_serde::Error> {
self.inner().deserialize(seed)
}
}
pub struct Change<T> {
pub old: Arc<T>,
pub new: Arc<T>,
pub changed: Vec<String>,
}
type Callback<T> = Arc<dyn Fn(&Change<T>) + Send + Sync>;
type Validator<T> = Box<dyn Fn(&T) -> Result<(), BoxError> + Send + Sync>;
pub struct LiveConfig<T> {
config: Config,
current: ArcSwap<T>,
reload_lock: Mutex<()>,
callbacks: Mutex<Vec<Callback<T>>>,
validator: Option<Validator<T>>,
}
impl<T: Provenance> LiveConfig<T> {
pub fn new(config: Config) -> Result<Self, Error> {
Self::create(config, None)
}
pub fn with_validator<E: Into<BoxError>>(
config: Config,
validator: impl Fn(&T) -> Result<(), E> + Send + Sync + 'static,
) -> Result<Self, Error> {
Self::create(
config,
Some(Box::new(move |config| {
validator(config).map_err(Into::into)
})),
)
}
fn create(config: Config, validator: Option<Validator<T>>) -> Result<Self, Error> {
let first = Self::checked_build(&config, validator.as_ref())?;
Ok(Self {
config,
current: ArcSwap::from_pointee(first),
reload_lock: Mutex::new(()),
callbacks: Mutex::new(Vec::new()),
validator,
})
}
fn checked_build(config: &Config, validator: Option<&Validator<T>>) -> Result<T, Error> {
let built = config.build::<T>()?;
if let Some(validator) = validator {
validator(&built).map_err(Error::Invalid)?;
}
Ok(built)
}
pub fn preview<S: Source + Send + Sync + 'static>(
&self,
dynamic: &DynamicSource,
data: S,
) -> Result<T, Error> {
let data: SourceArc = Arc::new(data);
PREVIEWS.with(|previews| previews.borrow_mut().push((dynamic.slot(), data)));
let _guard = PreviewGuard;
Self::checked_build(&self.config, self.validator.as_ref())
}
pub fn load(&self) -> Arc<T> {
self.current.load_full()
}
pub fn reload(&self) -> Result<Vec<String>, Error> {
let _guard = self
.reload_lock
.lock()
.unwrap_or_else(PoisonError::into_inner);
let new = Arc::new(Self::checked_build(&self.config, self.validator.as_ref())?);
let old = self.current.swap(new.clone());
let changed = changed_paths(&*old, &*new);
if !changed.is_empty() {
self.notify(&Change {
old,
new,
changed: changed.clone(),
});
}
Ok(changed)
}
pub fn on_change(&self, callback: impl Fn(&Change<T>) + Send + Sync + 'static) {
self.callbacks
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(Arc::new(callback));
}
fn notify(&self, change: &Change<T>) {
let callbacks = self
.callbacks
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
for callback in callbacks {
callback(change);
}
}
}
type LeafSnapshot = BTreeMap<String, (serde_json::Value, Category)>;
fn changed_paths<T: Provenance>(old: &T, new: &T) -> Vec<String> {
let old = snapshot(old);
snapshot(new)
.into_iter()
.filter(|(path, leaf)| old.get(path) != Some(leaf))
.map(|(path, _)| path)
.collect()
}
fn snapshot<T: Provenance>(config: &T) -> LeafSnapshot {
let mut leaves = BTreeMap::new();
config.walk_leaves("", &mut |path, value, category, _secret| {
let value = serde_json::to_value(value).unwrap_or_default();
leaves.insert(path.to_string(), (value, category));
});
leaves
}