use std::convert::Infallible;
use std::time::Duration;
use dynamic_config::dynamic_config;
use dynamic_config_tower::{sections, Snapshot, SnapshotLayer};
use http::{Request, Response};
use serde::Deserialize;
use tower::{service_fn, Layer, Service, ServiceExt};
#[dynamic_config]
#[derive(Debug, Deserialize)]
struct Server {
port: u16,
generation: u32,
}
#[dynamic_config]
#[derive(Debug, Deserialize)]
struct Features {
cache: bool,
generation: u32,
}
fn write(path: &std::path::Path, generation: u32) -> std::io::Result<()> {
std::fs::write(
path,
format!(
r#"{{"server": {{"port": 8080, "generation": {generation}}},
"features": {{"cache": true, "generation": {generation}}}}}"#
),
)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let path = directory.path().join("config.json");
let file = path.to_str().expect("a utf-8 path");
write(&path, 1)?;
Server::builder("server").file(file).init()?;
Features::builder("features").file(file).init()?;
let _watchers = [
Server::builder("server")
.file(file)
.watch(Duration::from_millis(50))?,
Features::builder("features")
.file(file)
.watch(Duration::from_millis(50))?,
];
let service = service_fn(|request: Request<()>| async move {
let snapshot = request
.extensions()
.get::<Snapshot>()
.expect("the layer ran");
let server = snapshot.require::<Server>().expect("listed and loaded");
let features = snapshot.require::<Features>().expect("listed and loaded");
Ok::<_, Infallible>(Response::new(format!(
"port {} cache {} — server generation {} / features generation {}",
server.port, features.cache, server.generation, features.generation
)))
});
let mut wired = SnapshotLayer::new(sections![Server, Features]).layer(service);
let answer = wired.ready().await?.call(Request::new(())).await?;
println!("one request, one reading:\n {}", answer.into_body());
write(&path, 2)?;
Server::builder("server").file(file).reload()?;
Features::builder("features").file(file).reload()?;
let answer = wired.ready().await?.call(Request::new(())).await?;
println!("after the reload:\n {}", answer.into_body());
println!("\nBoth numbers always match: a reload between the two `require`s");
println!("cannot tear a response, because neither read touches the engine.");
Ok(())
}