Skip to main content

core_snapshot/
core_snapshot.rs

1//! The snapshot, with no engine and no framework — which is the point.
2//!
3//! ```sh
4//! cargo run -p dynamic-config-web-core --example core_snapshot
5//! ```
6//!
7//! `Sections` never names a configuration type, only `Arc<T>`: a section
8//! is a closure answering "the current value", whatever owns it. Here
9//! that owner is a `Mutex` this example flips by hand, standing in for
10//! the engine — which is exactly how the crate's own tests prove the
11//! atomicity story without loading anything.
12
13use std::sync::{Arc, Mutex, OnceLock};
14
15use dynamic_config_web_core::Sections;
16
17#[derive(Debug)]
18struct Server {
19    port: u16,
20    generation: u32,
21}
22
23#[derive(Debug)]
24struct Features {
25    cache: bool,
26    generation: u32,
27}
28
29/// The stand-in engine: two "configurations" that reload together.
30fn state() -> &'static Mutex<(Arc<Server>, Arc<Features>)> {
31    static STATE: OnceLock<Mutex<(Arc<Server>, Arc<Features>)>> = OnceLock::new();
32
33    STATE.get_or_init(|| {
34        Mutex::new((
35            Arc::new(Server {
36                port: 8080,
37                generation: 1,
38            }),
39            Arc::new(Features {
40                cache: false,
41                generation: 1,
42            }),
43        ))
44    })
45}
46
47fn reload(generation: u32) {
48    let mut guard = state().lock().expect("not poisoned");
49
50    *guard = (
51        Arc::new(Server {
52            port: 8080 + u16::try_from(generation).unwrap_or(0),
53            generation,
54        }),
55        Arc::new(Features {
56            cache: generation.is_multiple_of(2),
57            generation,
58        }),
59    );
60}
61
62fn main() {
63    let sections = Sections::new()
64        .section(|| Some(state().lock().expect("not poisoned").0.clone()))
65        .section(|| Some(state().lock().expect("not poisoned").1.clone()));
66
67    println!("sections: {:?}\n", sections.names());
68
69    // One `take()` is one reading of every section. Everything read
70    // *through the snapshot* afterwards is from that instant — a reload
71    // in between cannot tear it.
72    let snapshot = sections.take();
73
74    let server = snapshot.require::<Server>().expect("in scope");
75
76    reload(2); // ← a deployment lands mid-request
77
78    let features = snapshot.require::<Features>().expect("in scope");
79
80    println!("through one snapshot, across a reload:");
81    println!(
82        "  server   = port {}, generation {}",
83        server.port, server.generation
84    );
85    println!(
86        "  features = cache {}, generation {} (same instant)\n",
87        features.cache, features.generation
88    );
89    assert_eq!(server.generation, features.generation);
90
91    // The next request's snapshot sees the new state — this is
92    // per-request pinning, not staleness.
93    let next = sections.take();
94
95    println!("the next snapshot:");
96    println!(
97        "  features = {:?}\n",
98        next.require::<Features>().expect("in scope")
99    );
100
101    // And the error half: a type nobody declared answers by name.
102    let missing = snapshot.require::<String>().unwrap_err();
103
104    println!("asking for an undeclared section:\n  {missing}");
105}