use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use dynamic_config::{dynamic_config, Error, Fetched, Format, RemoteSource};
use serde::Deserialize;
#[dynamic_config(files = ["dynamic-config/examples/app.json"], key = "server", env = "APP_")]
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
}
struct FakeStore {
document: Mutex<String>,
reads: AtomicUsize,
reachable: Mutex<bool>,
}
impl FakeStore {
fn new(document: &str) -> Self {
Self {
document: Mutex::new(document.to_owned()),
reads: AtomicUsize::new(0),
reachable: Mutex::new(true),
}
}
}
impl RemoteSource for FakeStore {
fn fetch(&self) -> Result<Fetched, Error> {
self.reads.fetch_add(1, Ordering::SeqCst);
if !*self.reachable.lock().unwrap() {
return Err(Error::remote("the store is unreachable"));
}
Ok(Fetched::new(
self.document.lock().unwrap().clone(),
Format::Json,
))
}
fn describe(&self) -> String {
"fake-store://in-process".to_owned()
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = std::sync::Arc::new(FakeStore::new(r#"{"server": {"port": 8443}}"#));
ServerConfig::set_remote(Handle(std::sync::Arc::clone(&store)));
println!(
"after set_remote: reads = {}",
store.reads.load(Ordering::SeqCst)
);
let config = ServerConfig::load()?;
println!(
"after load: reads = {}",
store.reads.load(Ordering::SeqCst)
);
println!(
" host = {}, port = {} (both from the file)",
config.host, config.port
);
ServerConfig::refresh_remote()?;
println!(
"\nafter refresh: reads = {}",
store.reads.load(Ordering::SeqCst)
);
let config = ServerConfig::load()?;
println!(
"after load: reads = {}",
store.reads.load(Ordering::SeqCst)
);
println!(" port = {} (the store wins over the file)", config.port);
if let Some(origin) = ServerConfig::source_of("port")? {
println!(" port comes {origin}");
}
std::env::set_var("APP_SERVER_PORT", "9999");
let config = ServerConfig::load()?;
println!("\nwith APP_SERVER_PORT=9999:");
println!(" port = {} (the environment beats the store)", config.port);
std::env::remove_var("APP_SERVER_PORT");
*store.reachable.lock().unwrap() = false;
let failure = ServerConfig::refresh_remote().unwrap_err();
println!("\nstore went away: {failure}");
let config = ServerConfig::load()?;
println!(
" port = {} — the last document it handed back is still serving",
config.port
);
ServerConfig::clear_remote();
let config = ServerConfig::load()?;
println!("\nafter clear_remote:");
println!(" port = {} (back to the file)", config.port);
watching()?;
Ok(())
}
fn watching() -> Result<(), Box<dyn std::error::Error>> {
use dynamic_config::RemoteWatch;
println!("\n--- watching ---\n");
ServerConfig::init()?;
ServerConfig::on_reload(|previous, current| {
println!(" hook: port {} -> {}", previous.port, current.port);
});
let watch = RemoteWatch::new();
let watching = watch.watching();
let pushes = std::thread::spawn(move || {
for port in 9001..9020 {
if !watching.keep_going() {
println!(" loop: stopped after being told to");
return;
}
let document =
Fetched::new(format!(r#"{{"server": {{"port": {port}}}}}"#), Format::Json);
if let Err(error) = ServerConfig::apply_remote(document) {
println!(" loop: the store pushed something unusable: {error}");
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
});
std::thread::sleep(std::time::Duration::from_millis(120));
watch.stop();
pushes.join().expect("the loop should end, not hang");
println!("\nfinal port = {}", ServerConfig::current().port);
Ok(())
}
struct Handle(std::sync::Arc<FakeStore>);
impl RemoteSource for Handle {
fn fetch(&self) -> Result<Fetched, Error> {
self.0.fetch()
}
fn describe(&self) -> String {
self.0.describe()
}
}