43
43
//! // ... test logic here ...
44
44
//! }
45
45
//! ```
46
+ use std:: collections:: HashMap ;
46
47
use std:: marker:: PhantomData ;
47
48
48
49
use super :: * ;
@@ -87,20 +88,6 @@ fn priority(s: Source) -> u8 {
87
88
}
88
89
}
89
90
90
- /// A single configuration layer in the global store.
91
- ///
92
- /// Each `Layer` wraps a [`Source`] and its associated [`Attrs`]
93
- /// values. Layers are kept in priority order and consulted during
94
- /// resolution.
95
- #[ derive( Clone ) ]
96
- struct Layer {
97
- /// The origin of this layer (File, Env, Runtime, or
98
- /// TestOverride).
99
- source : Source ,
100
- /// The set of attributes explicitly provided by this source.
101
- attrs : Attrs ,
102
- }
103
-
104
91
/// The full set of configuration layers in priority order.
105
92
///
106
93
/// `Layers` wraps a vector of [`Layer`]s, always kept sorted by
@@ -115,6 +102,209 @@ struct Layers {
115
102
ordered : Vec < Layer > ,
116
103
}
117
104
105
+ /// A single configuration layer in the global configuration model.
106
+ ///
107
+ /// Layers are consulted in priority order (`TestOverride → Runtime →
108
+ /// Env → File → Default`) when resolving configuration values. Each
109
+ /// variant holds an [`Attrs`] map of key/value pairs.
110
+ ///
111
+ /// The `TestOverride` variant additionally maintains per-key override
112
+ /// stacks to support nested and out-of-order test overrides. These
113
+ /// stacks are currently placeholders for a future refactor; for now,
114
+ /// only the `attrs` field is used.
115
+ ///
116
+ /// Variants:
117
+ /// - [`Layer::File`] — Values loaded from configuration files (lowest
118
+ /// explicit priority).
119
+ /// - [`Layer::Env`] — Values sourced from process environment
120
+ /// variables.
121
+ /// - [`Layer::Runtime`] — Programmatically set runtime overrides.
122
+ /// - [`Layer::TestOverride`] — Temporary in-test overrides applied
123
+ /// under [`ConfigLock`].
124
+ ///
125
+ /// Layers are stored in [`Layers::ordered`], kept sorted by their
126
+ /// effective [`Source`] priority (`TestOverride` first, `File` last).
127
+ enum Layer {
128
+ /// Values loaded from configuration files. Lowest explicit
129
+ /// priority; only overridden by Env, Runtime, or TestOverride.
130
+ File ( Attrs ) ,
131
+
132
+ /// Values read from process environment variables. Typically
133
+ /// installed at startup via [`init_from_env`].
134
+ Env ( Attrs ) ,
135
+
136
+ /// Values set programmatically at runtime. Stable high-priority
137
+ /// layer used by parent/child bootstrap and dynamic updates.
138
+ Runtime ( Attrs ) ,
139
+
140
+ /// Ephemeral values inserted during tests via
141
+ /// [`ConfigLock::override_key`]. Always takes precedence over all
142
+ /// other layers. Currently holds both the active `attrs` map and
143
+ /// a per-key `stacks` table (used to support nested or
144
+ /// out-of-order test overrides in future refactors).
145
+ TestOverride {
146
+ attrs : Attrs ,
147
+ stacks : HashMap < & ' static str , OverrideStack > ,
148
+ } ,
149
+ }
150
+
151
+ /// A per-key stack of test overrides used by the
152
+ /// [`Layer::TestOverride`] layer.
153
+ ///
154
+ /// Each stack tracks the sequence of active overrides applied to a
155
+ /// single configuration key. The topmost frame represents the
156
+ /// currently effective override; earlier frames represent older
157
+ /// (still live) guards that may drop out of order.
158
+ ///
159
+ /// Fields:
160
+ /// - `env_var`: The associated process environment variable name, if
161
+ /// any.
162
+ /// - `saved_env`: The original environment variable value before the
163
+ /// first override was applied (or `None` if it did not exist).
164
+ /// - `frames`: The stack of active override frames, with the top
165
+ /// being the last element in the vector.
166
+ ///
167
+ /// The full stack mechanism is not yet active; it is introduced
168
+ /// incrementally to prepare for robust out-of-order test override
169
+ /// restoration.
170
+ struct OverrideStack {
171
+ /// The name of the process environment variable associated with
172
+ /// this configuration key, if any.
173
+ ///
174
+ /// Used to mirror changes to the environment when overrides are
175
+ /// applied or removed. `None` if the key has no
176
+ /// `CONFIG.env_name`.
177
+ env_var : Option < String > ,
178
+
179
+ /// The original value of the environment variable before the
180
+ /// first override was applied.
181
+ ///
182
+ /// Stored so it can be restored once the last frame is dropped.
183
+ /// `None` means the variable did not exist prior to overriding.
184
+ saved_env : Option < String > ,
185
+
186
+ /// The sequence of active override frames for this key.
187
+ ///
188
+ /// Each frame represents one active test override; the last
189
+ /// element (`frames.last()`) is the current top-of-stack and
190
+ /// defines the effective value seen in the configuration and
191
+ /// environment.
192
+ frames : Vec < OverrideFrame > ,
193
+ }
194
+
195
+ /// A single entry in a per-key override stack.
196
+ ///
197
+ /// Each `OverrideFrame` represents one active test override applied
198
+ /// via [`ConfigLock::override_key`]. Frames are uniquely identified
199
+ /// by a monotonically increasing token and record both the value
200
+ /// being overridden and its string form for environment mirroring.
201
+ ///
202
+ /// When a guard drops, its frame is removed from the stack; if it was
203
+ /// the top, the next frame (if any) becomes active, or the original
204
+ /// environment is restored when the stack becomes empty.
205
+ struct OverrideFrame {
206
+ /// A unique, monotonically increasing identifier for this
207
+ /// override frame.
208
+ ///
209
+ /// Used to associate a dropping [`ConfigValueGuard`] with its
210
+ /// corresponding entry in the stack, even if drops occur out of
211
+ /// order.
212
+ token : u64 ,
213
+
214
+ /// The serialized configuration value active while this frame is
215
+ /// on top of its stack.
216
+ ///
217
+ /// Stored as a boxed [`SerializableValue`] to match how values
218
+ /// are kept within [`Attrs`].
219
+ value : Box < dyn crate :: attrs:: SerializableValue > ,
220
+
221
+ /// Pre-rendered string form of the value, used for environment
222
+ /// variable updates when this frame becomes active.
223
+ ///
224
+ /// Avoids recomputing `value.display()` on every push or pop.
225
+ env_str : String ,
226
+ }
227
+
228
+ /// Return the [`Source`] corresponding to a given [`Layer`].
229
+ ///
230
+ /// This provides a uniform way to retrieve a layer's logical source
231
+ /// (File, Env, Runtime, or TestOverride) regardless of its internal
232
+ /// representation. Used for sorting layers by priority and for
233
+ /// source-based lookups or removals.
234
+ fn layer_source ( l : & Layer ) -> Source {
235
+ match l {
236
+ Layer :: File ( _) => Source :: File ,
237
+ Layer :: Env ( _) => Source :: Env ,
238
+ Layer :: Runtime ( _) => Source :: Runtime ,
239
+ Layer :: TestOverride { .. } => Source :: TestOverride ,
240
+ }
241
+ }
242
+
243
+ /// Return an immutable reference to the [`Attrs`] contained in a
244
+ /// [`Layer`].
245
+ ///
246
+ /// This abstracts over the specific layer variant so callers can read
247
+ /// configuration values uniformly without needing to pattern-match on
248
+ /// the layer type. For `TestOverride`, this returns the current
249
+ /// top-level attributes reflecting the active overrides.
250
+ fn layer_attrs ( l : & Layer ) -> & Attrs {
251
+ match l {
252
+ Layer :: File ( a) | Layer :: Env ( a) | Layer :: Runtime ( a) => a,
253
+ Layer :: TestOverride { attrs, .. } => attrs,
254
+ }
255
+ }
256
+
257
+ /// Return a mutable reference to the [`Attrs`] contained in a
258
+ /// [`Layer`].
259
+ ///
260
+ /// This allows callers to modify configuration values within any
261
+ /// layer without needing to pattern-match on its variant. For
262
+ /// `TestOverride`, the returned [`Attrs`] always reflect the current
263
+ /// top-of-stack overrides for each key.
264
+ fn layer_attrs_mut ( l : & mut Layer ) -> & mut Attrs {
265
+ match l {
266
+ Layer :: File ( a) | Layer :: Env ( a) | Layer :: Runtime ( a) => a,
267
+ Layer :: TestOverride { attrs, .. } => attrs,
268
+ }
269
+ }
270
+
271
+ /// Return the index of the [`Layer::TestOverride`] within the
272
+ /// [`Layers`] vector.
273
+ ///
274
+ /// If a TestOverride layer is present, its position in the ordered
275
+ /// list is returned; otherwise, `None` is returned. This is used to
276
+ /// locate the active test override layer for inserting or restoring
277
+ /// temporary configuration values.
278
+ fn test_override_index ( layers : & Layers ) -> Option < usize > {
279
+ layers
280
+ . ordered
281
+ . iter ( )
282
+ . position ( |l| matches ! ( l, Layer :: TestOverride { .. } ) )
283
+ }
284
+
285
+ /// Ensure a [`Layer::TestOverride`] exists and return a mutable
286
+ /// reference to its [`Attrs`].
287
+ ///
288
+ /// If the TestOverride layer already exists, a mutable reference to
289
+ /// its attributes is returned directly. Otherwise, a new TestOverride
290
+ /// layer (with empty `Attrs` and stacks) is created, inserted into
291
+ /// the ordered layers according to priority, and then returned.
292
+ ///
293
+ /// This helper is used by [`ConfigLock::override_key`] to guarantee
294
+ /// that test overrides always have a dedicated writable layer.
295
+ fn ensure_test_override_layer_mut < ' a > ( layers : & ' a mut Layers ) -> & ' a mut Attrs {
296
+ if let Some ( i) = test_override_index ( layers) {
297
+ return layer_attrs_mut ( & mut layers. ordered [ i] ) ;
298
+ }
299
+ layers. ordered . push ( Layer :: TestOverride {
300
+ attrs : Attrs :: new ( ) ,
301
+ stacks : HashMap :: new ( ) ,
302
+ } ) ;
303
+ layers. ordered . sort_by_key ( |l| priority ( layer_source ( l) ) ) ;
304
+ let i = test_override_index ( layers) . expect ( "just inserted TestOverride layer" ) ;
305
+ layer_attrs_mut ( & mut layers. ordered [ i] )
306
+ }
307
+
118
308
/// Global layered configuration store.
119
309
///
120
310
/// This is the single authoritative store for configuration in
@@ -138,10 +328,7 @@ struct Layers {
138
328
static LAYERS : LazyLock < Arc < RwLock < Layers > > > = LazyLock :: new ( || {
139
329
let env = super :: from_env ( ) ;
140
330
let layers = Layers {
141
- ordered : vec ! [ Layer {
142
- source: Source :: Env ,
143
- attrs: env,
144
- } ] ,
331
+ ordered : vec ! [ Layer :: Env ( env) ] ,
145
332
} ;
146
333
Arc :: new ( RwLock :: new ( layers) )
147
334
} ) ;
@@ -214,8 +401,9 @@ pub fn init_from_yaml<P: AsRef<Path>>(path: P) -> Result<(), anyhow::Error> {
214
401
pub fn get < T : AttrValue + Copy > ( key : Key < T > ) -> T {
215
402
let layers = LAYERS . read ( ) . unwrap ( ) ;
216
403
for layer in & layers. ordered {
217
- if layer. attrs . contains_key ( key) {
218
- return * layer. attrs . get ( key) . unwrap ( ) ;
404
+ let a = layer_attrs ( layer) ;
405
+ if let Some ( value) = a. get ( key) {
406
+ return * value;
219
407
}
220
408
}
221
409
* key. default ( ) . expect ( "key must have a default" )
@@ -240,8 +428,9 @@ pub fn get_cloned<T: AttrValue>(key: Key<T>) -> T {
240
428
pub fn try_get_cloned < T : AttrValue > ( key : Key < T > ) -> Option < T > {
241
429
let layers = LAYERS . read ( ) . unwrap ( ) ;
242
430
for layer in & layers. ordered {
243
- if layer. attrs . contains_key ( key) {
244
- return layer. attrs . get ( key) . cloned ( ) ;
431
+ let a = layer_attrs ( layer) ;
432
+ if a. contains_key ( key) {
433
+ return a. get ( key) . cloned ( ) ;
245
434
}
246
435
}
247
436
key. default ( ) . cloned ( )
@@ -261,12 +450,20 @@ pub fn try_get_cloned<T: AttrValue>(key: Key<T>) -> Option<T> {
261
450
/// overriding configuration values.
262
451
pub fn set ( source : Source , attrs : Attrs ) {
263
452
let mut g = LAYERS . write ( ) . unwrap ( ) ;
264
- if let Some ( l) = g. ordered . iter_mut ( ) . find ( |l| l . source == source) {
265
- l . attrs = attrs;
453
+ if let Some ( l) = g. ordered . iter_mut ( ) . find ( |l| layer_source ( l ) == source) {
454
+ * layer_attrs_mut ( l ) = attrs;
266
455
} else {
267
- g. ordered . push ( Layer { source, attrs } ) ;
456
+ g. ordered . push ( match source {
457
+ Source :: File => Layer :: File ( attrs) ,
458
+ Source :: Env => Layer :: Env ( attrs) ,
459
+ Source :: Runtime => Layer :: Runtime ( attrs) ,
460
+ Source :: TestOverride => Layer :: TestOverride {
461
+ attrs,
462
+ stacks : HashMap :: new ( ) ,
463
+ } ,
464
+ } ) ;
268
465
}
269
- g. ordered . sort_by_key ( |l| priority ( l . source ) ) ; // TestOverride < Runtime < Env < File
466
+ g. ordered . sort_by_key ( |l| priority ( layer_source ( l ) ) ) ; // TestOverride < Runtime < Env < File
270
467
}
271
468
272
469
/// Remove the configuration layer for the given [`Source`], if
@@ -279,7 +476,7 @@ pub fn set(source: Source, attrs: Attrs) {
279
476
#[ allow( dead_code) ]
280
477
pub ( crate ) fn clear ( source : Source ) {
281
478
let mut g = LAYERS . write ( ) . unwrap ( ) ;
282
- g. ordered . retain ( |l| l . source != source) ;
479
+ g. ordered . retain ( |l| layer_source ( l ) != source) ;
283
480
}
284
481
285
482
/// Return a complete, merged snapshot of the effective configuration
@@ -311,7 +508,7 @@ pub fn attrs() -> Attrs {
311
508
// Try to resolve from highest -> lowest priority layer.
312
509
let mut chosen: Option < Box < dyn crate :: attrs:: SerializableValue > > = None ;
313
510
for layer in & layers. ordered {
314
- if let Some ( v) = layer. attrs . get_value_by_name ( name) {
511
+ if let Some ( v) = layer_attrs ( layer) . get_value_by_name ( name) {
315
512
chosen = Some ( v. cloned ( ) ) ;
316
513
break ;
317
514
}
@@ -351,26 +548,6 @@ pub fn reset_to_defaults() {
351
548
g. ordered . clear ( ) ;
352
549
}
353
550
354
- fn test_override_index ( layers : & Layers ) -> Option < usize > {
355
- layers
356
- . ordered
357
- . iter ( )
358
- . position ( |l| matches ! ( l. source, Source :: TestOverride ) )
359
- }
360
-
361
- fn ensure_test_override_layer_mut ( layers : & mut Layers ) -> & mut Attrs {
362
- if let Some ( i) = test_override_index ( layers) {
363
- return & mut layers. ordered [ i] . attrs ;
364
- }
365
- layers. ordered . push ( Layer {
366
- source : Source :: TestOverride ,
367
- attrs : Attrs :: new ( ) ,
368
- } ) ;
369
- layers. ordered . sort_by_key ( |l| priority ( l. source ) ) ;
370
- let i = test_override_index ( layers) . expect ( "just inserted TestOverride layer" ) ;
371
- & mut layers. ordered [ i] . attrs
372
- }
373
-
374
551
/// A guard that holds the global configuration lock and provides
375
552
/// override functionality.
376
553
///
@@ -443,15 +620,9 @@ impl ConfigLock {
443
620
impl Drop for ConfigLock {
444
621
fn drop ( & mut self ) {
445
622
let mut guard = LAYERS . write ( ) . unwrap ( ) ;
446
- if let Some ( pos) = guard
447
- . ordered
448
- . iter ( )
449
- . position ( |l| matches ! ( l. source, Source :: TestOverride ) )
450
- {
623
+ if let Some ( pos) = test_override_index ( & guard) {
451
624
guard. ordered . remove ( pos) ;
452
625
}
453
- // No need to restore anything else; underlying layers
454
- // remain intact.
455
626
}
456
627
}
457
628
@@ -486,7 +657,7 @@ impl<T: 'static> Drop for ConfigValueGuard<'_, T> {
486
657
let mut guard = LAYERS . write ( ) . unwrap ( ) ;
487
658
488
659
if let Some ( i) = test_override_index ( & guard) {
489
- let layer_attrs = & mut guard. ordered [ i] . attrs ;
660
+ let layer_attrs = & mut layer_attrs_mut ( & mut guard. ordered [ i] ) ;
490
661
491
662
if let Some ( prev) = self . orig . take ( ) {
492
663
layer_attrs. insert_value ( self . key , prev) ;
@@ -497,11 +668,9 @@ impl<T: 'static> Drop for ConfigValueGuard<'_, T> {
497
668
}
498
669
499
670
if let Some ( ( k, v) ) = self . orig_env . take ( ) {
500
- // SAFETY: process-global environment variables are
501
- // not thread-safe to mutate. This override/restore
502
- // path is only ever used in single-threaded test
503
- // code, and is serialized by the global ConfigLock to
504
- // avoid races between tests.
671
+ // SAFETY: only ever used in single-threaded test code and
672
+ // serialized by the global ConfigLock to avoid races
673
+ // between tests.
505
674
unsafe {
506
675
if let Some ( v) = v {
507
676
std:: env:: set_var ( k, v) ;
0 commit comments