Skip to main content

mz_dyncfg/
lib.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Dynamically updatable configuration.
11//!
12//! Basic usage:
13//! - A type-safe static `Config` is defined near where it is used.
14//! - Once in the lifetime of a process, all interesting `Config`s are
15//!   registered to a `ConfigSet`. The values within a `ConfigSet` are shared,
16//!   though multiple `ConfigSet`s may be created and each are completely
17//!   independent (i.e. one in each unit test).
18//! - A `ConfigSet` is plumbed around as necessary and may be used to get or
19//!   set the value of `Config`.
20//!
21//! ```
22//! # use mz_dyncfg::{Config, ConfigSet};
23//! const FOO: Config<bool> = Config::new("foo", false, "description of foo");
24//! fn bar(cfg: &ConfigSet) {
25//!     assert_eq!(FOO.get(&cfg), false);
26//! }
27//! fn main() {
28//!     let cfg = ConfigSet::default().add(&FOO);
29//!     bar(&cfg);
30//! }
31//! ```
32//!
33//! # Design considerations for this library
34//!
35//! - The primary motivation is minimal boilerplate. Runtime dynamic
36//!   configuration is one of the most powerful tools we have to quickly react
37//!   to incidents, etc in production. Adding and using them should be easy
38//!   enough that engineers feel empowered to use them generously.
39//!
40//!   The theoretical minimum boilerplate is 1) declare a config and 2) use a
41//!   config to get/set the value. These could be combined into one step if (2)
42//!   were based on global state, but that doesn't play well with testing. So
43//!   instead we accomplish (2) by constructing a shared bag of config values in
44//!   each `fn main`, amortizing the cost by plumbing it once to each component
45//!   (not once per config).
46//! - Config definitions are kept next to the usage. The common case is that a
47//!   config is used in only one place and this makes it easy to see the
48//!   associated documentation at the usage site. Configs that are used in
49//!   multiple places may be defined in some common place as appropriate.
50//! - Everything is type-safe.
51//! - Secondarily: set up the ability to get and use the latest values of
52//!   configs in tooling like `persistcli` and `stash debug`. As we've embraced
53//!   dynamic configuration, we've ended up in a situation where it's stressful
54//!   to run the read-write `persistcli admin` tooling with the defaults
55//!   compiled into code, but `persistcli` doesn't have access to the vars stuff
56//!   and doesn't want to instantiate a catalog impl.
57
58use std::collections::BTreeMap;
59use std::marker::PhantomData;
60use std::sync::atomic::Ordering::SeqCst;
61use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
62use std::sync::{Arc, RwLock};
63use std::time::Duration;
64
65use serde::{Deserialize, Serialize};
66use tracing::error;
67
68/// A handle to a dynamically updatable configuration value.
69///
70/// This represents a strongly-typed named config of type `T`. It may be
71/// registered to a set of such configs with [ConfigSet::add] and then later
72/// used to retrieve the latest value at any time with [Self::get].
73///
74/// The supported types are [bool], [usize], [Duration], and [String], as well as [Option]
75/// variants of these as necessary.
76#[derive(Clone, Debug)]
77pub struct Config<D: ConfigDefault> {
78    name: &'static str,
79    desc: &'static str,
80    default: D,
81}
82
83impl<D: ConfigDefault> Config<D> {
84    /// Constructs a handle for a config of type `T`.
85    ///
86    /// It is best practice, but not strictly required, for the name to be
87    /// globally unique within a process.
88    ///
89    /// TODO(cfg): Add some sort of categorization of config purpose here: e.g.
90    /// limited-lifetime rollout flag, CYA, magic number that we never expect to
91    /// tune, magic number that we DO expect to tune, etc. This could be used to
92    /// power something like a `--future-default-flags` for CI, to replace part
93    /// or all of the manually maintained list.
94    ///
95    /// TODO(cfg): See if we can make this more Rust-y and take these params as
96    /// a struct (the obvious thing hits some issues with const combined with
97    /// Drop).
98    pub const fn new(name: &'static str, default: D, desc: &'static str) -> Self {
99        Config {
100            name,
101            default,
102            desc,
103        }
104    }
105
106    /// The name of this config.
107    pub fn name(&self) -> &str {
108        self.name
109    }
110
111    /// The description of this config.
112    pub fn desc(&self) -> &str {
113        self.desc
114    }
115
116    /// The default value of this config.
117    pub fn default(&self) -> &D {
118        &self.default
119    }
120
121    /// Returns the latest value of this config within the given set.
122    ///
123    /// Panics if this config was not previously registered to the set.
124    ///
125    /// TODO(cfg): Decide if this should be a method on `ConfigSet` instead to
126    /// match the precedent of `BTreeMap/HashMap::get` taking a key. It's like
127    /// this initially because it was thought that the `Config` definition was
128    /// the more important "noun" and also that rustfmt would maybe work better
129    /// on this ordering.
130    pub fn get(&self, set: &ConfigSet) -> D::ConfigType {
131        D::ConfigType::from_val(self.shared(set).load())
132    }
133
134    /// Returns a handle to the value of this config in the given set.
135    ///
136    /// This allows users to amortize the cost of the name lookup.
137    pub fn handle(&self, set: &ConfigSet) -> ConfigValHandle<D::ConfigType> {
138        ConfigValHandle {
139            val: self.shared(set).clone(),
140            _type: PhantomData,
141        }
142    }
143
144    /// Returns the shared value of this config in the given set.
145    fn shared<'a>(&self, set: &'a ConfigSet) -> &'a ConfigValAtomic {
146        &set.configs
147            .get(self.name)
148            .unwrap_or_else(|| panic!("config {} should be registered to set", self.name))
149            .val
150    }
151
152    /// Parse a string value for this config.
153    pub fn parse_val(&self, val: &str) -> Result<ConfigVal, String> {
154        let val = D::ConfigType::parse(val)?;
155        let val = Into::<ConfigVal>::into(val);
156        Ok(val)
157    }
158}
159
160/// A type usable as a [Config].
161pub trait ConfigType: Into<ConfigVal> + Clone + Sized {
162    /// Converts a type-erased enum value to this type.
163    ///
164    /// Panics if the enum's variant does not match this type.
165    fn from_val(val: ConfigVal) -> Self;
166
167    /// Parses this string slice into a [`ConfigType`].
168    fn parse(s: &str) -> Result<Self, String>;
169}
170
171/// A trait for a type that can be used as a default for a [`Config`].
172pub trait ConfigDefault: Clone {
173    type ConfigType: ConfigType;
174
175    /// Converts into the config type.
176    fn into_config_type(self) -> Self::ConfigType;
177}
178
179impl<T: ConfigType> ConfigDefault for T {
180    type ConfigType = T;
181
182    fn into_config_type(self) -> T {
183        self
184    }
185}
186
187impl<T: ConfigType> ConfigDefault for fn() -> T {
188    type ConfigType = T;
189
190    fn into_config_type(self) -> T {
191        (self)()
192    }
193}
194
195/// An set of [Config]s with values that may or may not be independent of other
196/// [ConfigSet]s.
197///
198/// When constructing a ConfigSet from scratch with [ConfigSet::default]
199/// followed by [ConfigSet::add], the values added to the ConfigSet will be
200/// independent of the values in all other ConfigSets.
201///
202/// When constructing a ConfigSet by cloning an existing ConfigSet, any values
203/// cloned from the original ConfigSet will be shared with the original
204/// ConfigSet. Updates to these values in one ConfigSet will be seen in the
205/// other ConfigSet, and vice versa. Any value added to the new ConfigSet via
206/// ConfigSet::add will be independent of values in the original ConfigSet,
207/// unless the new ConfigSet is later cloned.
208#[derive(Clone, Default)]
209pub struct ConfigSet {
210    configs: BTreeMap<String, ConfigEntry>,
211}
212
213impl ConfigSet {
214    /// Adds the given config to this set.
215    ///
216    /// Names are required to be unique within a set, but each set is entirely
217    /// independent. The same `Config` may be registered to multiple
218    /// [`ConfigSet`]s and thus have independent values (e.g. imagine a unit
219    /// test executing concurrently in the same process).
220    ///
221    /// Panics if a config with the same name has been previously registered
222    /// to this set.
223    pub fn add<D: ConfigDefault>(mut self, config: &Config<D>) -> Self {
224        let default = config.default.clone().into_config_type();
225        let default = Into::<ConfigVal>::into(default);
226        let config = ConfigEntry {
227            name: config.name,
228            desc: config.desc,
229            default: default.clone(),
230            val: ConfigValAtomic::from(default),
231        };
232        if let Some(prev) = self.configs.insert(config.name.to_owned(), config) {
233            panic!("{} registered twice", prev.name);
234        }
235        self
236    }
237
238    /// Returns the configs currently registered to this set.
239    pub fn entries(&self) -> impl Iterator<Item = &ConfigEntry> {
240        self.configs.values()
241    }
242
243    /// Returns the config with `name` registered to this set, if one exists.
244    pub fn entry(&self, name: &str) -> Option<&ConfigEntry> {
245        self.configs.get(name)
246    }
247}
248
249/// An entry for a config in a [ConfigSet].
250#[derive(Clone, Debug)]
251pub struct ConfigEntry {
252    name: &'static str,
253    desc: &'static str,
254    default: ConfigVal,
255    val: ConfigValAtomic,
256}
257
258impl ConfigEntry {
259    /// The name of this config.
260    pub fn name(&self) -> &'static str {
261        self.name
262    }
263
264    /// The description of this config.
265    pub fn desc(&self) -> &'static str {
266        self.desc
267    }
268
269    /// The default value of this config.
270    ///
271    /// This value is never updated.
272    pub fn default(&self) -> &ConfigVal {
273        &self.default
274    }
275
276    /// The value of this config in the set.
277    pub fn val(&self) -> ConfigVal {
278        self.val.load()
279    }
280}
281
282/// A handle to a configuration value in a [`ConfigSet`].
283///
284/// Allows users to amortize the lookup of a name within a set.
285///
286/// Handles can be cheaply cloned.
287#[derive(Debug, Clone)]
288pub struct ConfigValHandle<T> {
289    val: ConfigValAtomic,
290    _type: PhantomData<T>,
291}
292
293impl<T: ConfigType> ConfigValHandle<T> {
294    /// Returns the latest value of this config within the set associated with
295    /// the handle.
296    pub fn get(&self) -> T {
297        T::from_val(self.val.load())
298    }
299
300    /// Return a new handle that returns the constant value provided,
301    /// generally for testing.
302    pub fn disconnected<X>(value: X) -> Self
303    where
304        X: ConfigDefault<ConfigType = T>,
305    {
306        let config_val: ConfigVal = value.into_config_type().into();
307        Self {
308            val: config_val.into(),
309            _type: Default::default(),
310        }
311    }
312}
313
314/// A type-erased configuration value for when set of different types are stored
315/// in a collection.
316#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
317pub enum ConfigVal {
318    /// A `bool` value.
319    Bool(bool),
320    /// A `u32` value.
321    U32(u32),
322    /// A `usize` value.
323    Usize(usize),
324    /// An `Option<usize>` value.
325    OptUsize(Option<usize>),
326    /// An `f64` value.
327    F64(f64),
328    /// A `String` value.
329    String(String),
330    /// An `Option<String>` value
331    OptString(Option<String>),
332    /// A `Duration` value.
333    Duration(Duration),
334    /// A JSON value.
335    #[serde(with = "serde_json_string")]
336    Json(serde_json::Value),
337}
338
339/// To make `ConfigVal` compatible with non-self-describing serialization formats like bincode,
340/// serialize JSON values as strings.
341mod serde_json_string {
342    use serde::de::{Deserialize, Deserializer, Error};
343    use serde::ser::Serializer;
344
345    pub fn serialize<S>(value: &serde_json::Value, serializer: S) -> Result<S::Ok, S::Error>
346    where
347        S: Serializer,
348    {
349        serializer.serialize_str(&value.to_string())
350    }
351
352    pub fn deserialize<'de, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
353    where
354        D: Deserializer<'de>,
355    {
356        let s = String::deserialize(deserializer)?;
357        serde_json::from_str(&s).map_err(D::Error::custom)
358    }
359}
360
361/// An atomic version of [`ConfigVal`] to allow configuration values to be
362/// shared between configuration writers and readers.
363///
364/// TODO(cfg): Consider moving these Arcs to be a single one around the map in
365/// `ConfigSet` instead. That would mean less pointer-chasing in the common
366/// case, but would remove the possibility of amortizing the name lookup via
367/// [Config::handle].
368#[derive(Clone, Debug)]
369enum ConfigValAtomic {
370    Bool(Arc<AtomicBool>),
371    U32(Arc<AtomicU32>),
372    Usize(Arc<AtomicUsize>),
373    OptUsize(Arc<RwLock<Option<usize>>>),
374    // Shared via to_bits/from_bits so we can use the atomic instead of Mutex.
375    F64(Arc<AtomicU64>),
376    String(Arc<RwLock<String>>),
377    OptString(Arc<RwLock<Option<String>>>),
378    Duration(Arc<RwLock<Duration>>),
379    Json(Arc<RwLock<serde_json::Value>>),
380}
381
382impl From<ConfigVal> for ConfigValAtomic {
383    fn from(val: ConfigVal) -> ConfigValAtomic {
384        match val {
385            ConfigVal::Bool(x) => ConfigValAtomic::Bool(Arc::new(AtomicBool::new(x))),
386            ConfigVal::U32(x) => ConfigValAtomic::U32(Arc::new(AtomicU32::new(x))),
387            ConfigVal::Usize(x) => ConfigValAtomic::Usize(Arc::new(AtomicUsize::new(x))),
388            ConfigVal::OptUsize(x) => ConfigValAtomic::OptUsize(Arc::new(RwLock::new(x))),
389            ConfigVal::F64(x) => ConfigValAtomic::F64(Arc::new(AtomicU64::new(x.to_bits()))),
390            ConfigVal::String(x) => ConfigValAtomic::String(Arc::new(RwLock::new(x))),
391            ConfigVal::OptString(x) => ConfigValAtomic::OptString(Arc::new(RwLock::new(x))),
392            ConfigVal::Duration(x) => ConfigValAtomic::Duration(Arc::new(RwLock::new(x))),
393            ConfigVal::Json(x) => ConfigValAtomic::Json(Arc::new(RwLock::new(x))),
394        }
395    }
396}
397
398impl ConfigValAtomic {
399    fn load(&self) -> ConfigVal {
400        match self {
401            ConfigValAtomic::Bool(x) => ConfigVal::Bool(x.load(SeqCst)),
402            ConfigValAtomic::U32(x) => ConfigVal::U32(x.load(SeqCst)),
403            ConfigValAtomic::Usize(x) => ConfigVal::Usize(x.load(SeqCst)),
404            ConfigValAtomic::OptUsize(x) => ConfigVal::OptUsize(*x.read().expect("lock poisoned")),
405            ConfigValAtomic::F64(x) => ConfigVal::F64(f64::from_bits(x.load(SeqCst))),
406            ConfigValAtomic::String(x) => {
407                ConfigVal::String(x.read().expect("lock poisoned").clone())
408            }
409            ConfigValAtomic::OptString(x) => {
410                ConfigVal::OptString(x.read().expect("lock poisoned").clone())
411            }
412            ConfigValAtomic::Duration(x) => ConfigVal::Duration(*x.read().expect("lock poisoned")),
413            ConfigValAtomic::Json(x) => ConfigVal::Json(x.read().expect("lock poisoned").clone()),
414        }
415    }
416
417    fn store(&self, val: ConfigVal) {
418        match (self, val) {
419            (ConfigValAtomic::Bool(x), ConfigVal::Bool(val)) => x.store(val, SeqCst),
420            (ConfigValAtomic::U32(x), ConfigVal::U32(val)) => x.store(val, SeqCst),
421            (ConfigValAtomic::Usize(x), ConfigVal::Usize(val)) => x.store(val, SeqCst),
422            (ConfigValAtomic::OptUsize(x), ConfigVal::OptUsize(val)) => {
423                *x.write().expect("lock poisoned") = val
424            }
425            (ConfigValAtomic::F64(x), ConfigVal::F64(val)) => x.store(val.to_bits(), SeqCst),
426            (ConfigValAtomic::String(x), ConfigVal::String(val)) => {
427                *x.write().expect("lock poisoned") = val
428            }
429            (ConfigValAtomic::OptString(x), ConfigVal::OptString(val)) => {
430                *x.write().expect("lock poisoned") = val
431            }
432            (ConfigValAtomic::Duration(x), ConfigVal::Duration(val)) => {
433                *x.write().expect("lock poisoned") = val
434            }
435            (ConfigValAtomic::Json(x), ConfigVal::Json(val)) => {
436                *x.write().expect("lock poisoned") = val
437            }
438            (ConfigValAtomic::Bool(_), val)
439            | (ConfigValAtomic::U32(_), val)
440            | (ConfigValAtomic::Usize(_), val)
441            | (ConfigValAtomic::OptUsize(_), val)
442            | (ConfigValAtomic::F64(_), val)
443            | (ConfigValAtomic::String(_), val)
444            | (ConfigValAtomic::OptString(_), val)
445            | (ConfigValAtomic::Duration(_), val)
446            | (ConfigValAtomic::Json(_), val) => {
447                panic!("attempted to store {val:?} value in {self:?} parameter")
448            }
449        }
450    }
451}
452
453/// A batch of value updates to [Config]s in a [ConfigSet].
454///
455/// This may be sent across processes to apply the same value updates, but may not be durably
456/// written down.
457#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
458pub struct ConfigUpdates {
459    pub updates: BTreeMap<String, ConfigVal>,
460}
461
462impl ConfigUpdates {
463    /// Adds an update for the given config and value.
464    ///
465    /// If a value of the same config has previously been added to these
466    /// updates, replaces it.
467    pub fn add<T, U>(&mut self, config: &Config<T>, val: U)
468    where
469        T: ConfigDefault,
470        U: ConfigDefault<ConfigType = T::ConfigType>,
471    {
472        self.add_dynamic(config.name, val.into_config_type().into());
473    }
474
475    /// Adds an update for the given configuration name and value.
476    ///
477    /// It is the callers responsibility to ensure the value is of the
478    /// appropriate type for the configuration.
479    ///
480    /// If a value of the same config has previously been added to these
481    /// updates, replaces it.
482    pub fn add_dynamic(&mut self, name: &str, val: ConfigVal) {
483        self.updates.insert(name.to_owned(), val);
484    }
485
486    /// Adds the entries in `other` to `self`, with `other` taking precedence.
487    pub fn extend(&mut self, mut other: Self) {
488        self.updates.append(&mut other.updates)
489    }
490
491    /// Applies these config updates to the given [ConfigSet].
492    ///
493    /// This doesn't need to be the same set that the value updates were added
494    /// from. In fact, the primary use of this is propagating config updates
495    /// across processes.
496    ///
497    /// The value updates for any configs unknown by the given set are skipped.
498    /// Ditto for config type mismatches. However, this is unexpected usage at
499    /// present and so is logged to Sentry.
500    pub fn apply(&self, set: &ConfigSet) {
501        for (name, val) in self.updates.iter() {
502            let Some(config) = set.configs.get(name) else {
503                error!("config update {} {:?} not known set: {:?}", name, val, set);
504                continue;
505            };
506            config.val.store(val.clone());
507        }
508    }
509}
510
511mod impls {
512    use std::num::{ParseFloatError, ParseIntError};
513    use std::str::ParseBoolError;
514    use std::time::Duration;
515
516    use crate::{ConfigDefault, ConfigSet, ConfigType, ConfigVal};
517
518    impl ConfigType for bool {
519        fn from_val(val: ConfigVal) -> Self {
520            match val {
521                ConfigVal::Bool(x) => x,
522                x => panic!("expected bool value got {:?}", x),
523            }
524        }
525
526        fn parse(s: &str) -> Result<Self, String> {
527            match s {
528                "on" => return Ok(true),
529                "off" => return Ok(false),
530                _ => {}
531            }
532            s.parse().map_err(|e: ParseBoolError| e.to_string())
533        }
534    }
535
536    impl From<bool> for ConfigVal {
537        fn from(val: bool) -> ConfigVal {
538            ConfigVal::Bool(val)
539        }
540    }
541
542    impl ConfigType for u32 {
543        fn from_val(val: ConfigVal) -> Self {
544            match val {
545                ConfigVal::U32(x) => x,
546                x => panic!("expected u32 value got {:?}", x),
547            }
548        }
549
550        fn parse(s: &str) -> Result<Self, String> {
551            s.parse().map_err(|e: ParseIntError| e.to_string())
552        }
553    }
554
555    impl From<u32> for ConfigVal {
556        fn from(val: u32) -> ConfigVal {
557            ConfigVal::U32(val)
558        }
559    }
560
561    impl ConfigType for usize {
562        fn from_val(val: ConfigVal) -> Self {
563            match val {
564                ConfigVal::Usize(x) => x,
565                x => panic!("expected usize value got {:?}", x),
566            }
567        }
568
569        fn parse(s: &str) -> Result<Self, String> {
570            s.parse().map_err(|e: ParseIntError| e.to_string())
571        }
572    }
573
574    impl From<usize> for ConfigVal {
575        fn from(val: usize) -> ConfigVal {
576            ConfigVal::Usize(val)
577        }
578    }
579
580    impl ConfigType for Option<usize> {
581        fn from_val(val: ConfigVal) -> Self {
582            match val {
583                ConfigVal::OptUsize(x) => x,
584                x => panic!("expected usize value got {:?}", x),
585            }
586        }
587
588        fn parse(s: &str) -> Result<Self, String> {
589            if s.is_empty() {
590                Ok(None)
591            } else {
592                let val = s.parse().map_err(|e: ParseIntError| e.to_string())?;
593                Ok(Some(val))
594            }
595        }
596    }
597
598    impl From<Option<usize>> for ConfigVal {
599        fn from(val: Option<usize>) -> ConfigVal {
600            ConfigVal::OptUsize(val)
601        }
602    }
603
604    impl ConfigType for f64 {
605        fn from_val(val: ConfigVal) -> Self {
606            match val {
607                ConfigVal::F64(x) => x,
608                x => panic!("expected f64 value got {:?}", x),
609            }
610        }
611
612        fn parse(s: &str) -> Result<Self, String> {
613            s.parse().map_err(|e: ParseFloatError| e.to_string())
614        }
615    }
616
617    impl From<f64> for ConfigVal {
618        fn from(val: f64) -> ConfigVal {
619            ConfigVal::F64(val)
620        }
621    }
622
623    impl ConfigType for String {
624        fn from_val(val: ConfigVal) -> Self {
625            match val {
626                ConfigVal::String(x) => x,
627                x => panic!("expected String value got {:?}", x),
628            }
629        }
630
631        fn parse(s: &str) -> Result<Self, String> {
632            Ok(s.to_string())
633        }
634    }
635
636    impl From<String> for ConfigVal {
637        fn from(val: String) -> ConfigVal {
638            ConfigVal::String(val)
639        }
640    }
641
642    impl ConfigDefault for &str {
643        type ConfigType = String;
644
645        fn into_config_type(self) -> String {
646            self.into()
647        }
648    }
649
650    impl ConfigType for Option<String> {
651        fn from_val(val: ConfigVal) -> Self {
652            match val {
653                ConfigVal::OptString(x) => x,
654                x => panic!("expected String value got {:?}", x),
655            }
656        }
657
658        fn parse(s: &str) -> Result<Self, String> {
659            Ok(Some(s.to_string()))
660        }
661    }
662
663    impl From<Option<String>> for ConfigVal {
664        fn from(val: Option<String>) -> ConfigVal {
665            ConfigVal::OptString(val)
666        }
667    }
668
669    impl ConfigDefault for Option<&str> {
670        type ConfigType = Option<String>;
671
672        fn into_config_type(self) -> Option<String> {
673            self.map(|s| s.to_string())
674        }
675    }
676
677    impl ConfigType for Duration {
678        fn from_val(val: ConfigVal) -> Self {
679            match val {
680                ConfigVal::Duration(x) => x,
681                x => panic!("expected Duration value got {:?}", x),
682            }
683        }
684
685        fn parse(s: &str) -> Result<Self, String> {
686            humantime::parse_duration(s).map_err(|e| e.to_string())
687        }
688    }
689
690    impl From<Duration> for ConfigVal {
691        fn from(val: Duration) -> ConfigVal {
692            ConfigVal::Duration(val)
693        }
694    }
695
696    impl ConfigType for serde_json::Value {
697        fn from_val(val: ConfigVal) -> Self {
698            match val {
699                ConfigVal::Json(x) => x,
700                x => panic!("expected JSON value got {:?}", x),
701            }
702        }
703
704        fn parse(s: &str) -> Result<Self, String> {
705            serde_json::from_str(s).map_err(|e| e.to_string())
706        }
707    }
708
709    impl From<serde_json::Value> for ConfigVal {
710        fn from(val: serde_json::Value) -> ConfigVal {
711            ConfigVal::Json(val)
712        }
713    }
714
715    impl std::fmt::Debug for ConfigSet {
716        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
717            let ConfigSet { configs } = self;
718            f.debug_map()
719                .entries(configs.iter().map(|(name, val)| (name, val.val())))
720                .finish()
721        }
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    use mz_ore::assert_err;
730
731    const BOOL: Config<bool> = Config::new("bool", true, "");
732    const U32: Config<u32> = Config::new("u32", 4, "");
733    const USIZE: Config<usize> = Config::new("usize", 1, "");
734    const OPT_USIZE: Config<Option<usize>> = Config::new("opt_usize", Some(2), "");
735    const F64: Config<f64> = Config::new("f64", 5.0, "");
736    const STRING: Config<&str> = Config::new("string", "a", "");
737    const OPT_STRING: Config<Option<&str>> = Config::new("opt_string", Some("a"), "");
738    const DURATION: Config<Duration> = Config::new("duration", Duration::from_nanos(3), "");
739    const JSON: Config<fn() -> serde_json::Value> =
740        Config::new("json", || serde_json::json!({}), "");
741
742    #[mz_ore::test]
743    fn all_types() {
744        let configs = ConfigSet::default()
745            .add(&BOOL)
746            .add(&USIZE)
747            .add(&U32)
748            .add(&OPT_USIZE)
749            .add(&F64)
750            .add(&STRING)
751            .add(&OPT_STRING)
752            .add(&DURATION)
753            .add(&JSON);
754        assert_eq!(BOOL.get(&configs), true);
755        assert_eq!(U32.get(&configs), 4);
756        assert_eq!(USIZE.get(&configs), 1);
757        assert_eq!(OPT_USIZE.get(&configs), Some(2));
758        assert_eq!(F64.get(&configs), 5.0);
759        assert_eq!(STRING.get(&configs), "a");
760        assert_eq!(OPT_STRING.get(&configs), Some("a".to_string()));
761        assert_eq!(DURATION.get(&configs), Duration::from_nanos(3));
762        assert_eq!(JSON.get(&configs), serde_json::json!({}));
763
764        let mut updates = ConfigUpdates::default();
765        updates.add(&BOOL, false);
766        updates.add(&U32, 7);
767        updates.add(&USIZE, 2);
768        updates.add(&OPT_USIZE, None::<usize>);
769        updates.add(&F64, 8.0);
770        updates.add(&STRING, "b");
771        updates.add(&OPT_STRING, None::<String>);
772        updates.add(&DURATION, Duration::from_nanos(4));
773        updates.add(&JSON, serde_json::json!({"a": 1}));
774        updates.apply(&configs);
775
776        assert_eq!(BOOL.get(&configs), false);
777        assert_eq!(U32.get(&configs), 7);
778        assert_eq!(USIZE.get(&configs), 2);
779        assert_eq!(OPT_USIZE.get(&configs), None);
780        assert_eq!(F64.get(&configs), 8.0);
781        assert_eq!(STRING.get(&configs), "b");
782        assert_eq!(OPT_STRING.get(&configs), None);
783        assert_eq!(DURATION.get(&configs), Duration::from_nanos(4));
784        assert_eq!(JSON.get(&configs), serde_json::json!({"a": 1}));
785    }
786
787    #[mz_ore::test]
788    fn fn_default() {
789        const BOOL_FN_DEFAULT: Config<fn() -> bool> = Config::new("bool", || !true, "");
790        const STRING_FN_DEFAULT: Config<fn() -> String> =
791            Config::new("string", || "x".repeat(3), "");
792
793        const OPT_STRING_FN_DEFAULT: Config<fn() -> Option<String>> =
794            Config::new("opt_string", || Some("x".repeat(3)), "");
795
796        let configs = ConfigSet::default()
797            .add(&BOOL_FN_DEFAULT)
798            .add(&STRING_FN_DEFAULT)
799            .add(&OPT_STRING_FN_DEFAULT);
800        assert_eq!(BOOL_FN_DEFAULT.get(&configs), false);
801        assert_eq!(STRING_FN_DEFAULT.get(&configs), "xxx");
802        assert_eq!(OPT_STRING_FN_DEFAULT.get(&configs), Some("xxx".to_string()));
803    }
804
805    #[mz_ore::test]
806    fn config_set() {
807        let c0 = ConfigSet::default().add(&USIZE);
808        assert_eq!(USIZE.get(&c0), 1);
809        let mut updates = ConfigUpdates::default();
810        updates.add(&USIZE, 2);
811        updates.apply(&c0);
812        assert_eq!(USIZE.get(&c0), 2);
813
814        // Each ConfigSet is independent, even if they contain the same set of
815        // configs.
816        let c1 = ConfigSet::default().add(&USIZE);
817        assert_eq!(USIZE.get(&c1), 1);
818        let mut updates = ConfigUpdates::default();
819        updates.add(&USIZE, 3);
820        updates.apply(&c1);
821        assert_eq!(USIZE.get(&c1), 3);
822        assert_eq!(USIZE.get(&c0), 2);
823
824        // We can copy values from one to the other, though (envd -> clusterd).
825        let mut updates = ConfigUpdates::default();
826        for e in c0.entries() {
827            updates.add_dynamic(e.name, e.val());
828        }
829        assert_eq!(USIZE.get(&c1), 3);
830        updates.apply(&c1);
831        assert_eq!(USIZE.get(&c1), 2);
832    }
833
834    #[mz_ore::test]
835    fn config_updates_extend() {
836        // Regression test for database-issues#7793.
837        //
838        // Construct two ConfigUpdates with overlapping, but not identical, sets
839        // of configs. Combine them and assert that the expected number of
840        // updates is present.
841        let mut u1 = {
842            let c = ConfigSet::default().add(&USIZE).add(&STRING);
843            let mut x = ConfigUpdates::default();
844            for e in c.entries() {
845                x.add_dynamic(e.name(), e.val());
846            }
847            x
848        };
849        let u2 = {
850            let c = ConfigSet::default().add(&USIZE).add(&DURATION);
851            let mut updates = ConfigUpdates::default();
852            updates.add(&USIZE, 2);
853            updates.apply(&c);
854            let mut x = ConfigUpdates::default();
855            for e in c.entries() {
856                x.add_dynamic(e.name(), e.val());
857            }
858            x
859        };
860        assert_eq!(u1.updates.len(), 2);
861        assert_eq!(u2.updates.len(), 2);
862        u1.extend(u2);
863        assert_eq!(u1.updates.len(), 3);
864
865        // Assert that extend kept the correct (later) value for the overlapping
866        // config.
867        let c = ConfigSet::default().add(&USIZE);
868        u1.apply(&c);
869        assert_eq!(USIZE.get(&c), 2);
870    }
871
872    #[mz_ore::test]
873    fn config_parse() {
874        assert_eq!(BOOL.parse_val("true"), Ok(ConfigVal::Bool(true)));
875        assert_eq!(BOOL.parse_val("on"), Ok(ConfigVal::Bool(true)));
876        assert_eq!(BOOL.parse_val("false"), Ok(ConfigVal::Bool(false)));
877        assert_eq!(BOOL.parse_val("off"), Ok(ConfigVal::Bool(false)));
878        assert_err!(BOOL.parse_val("42"));
879        assert_err!(BOOL.parse_val("66.6"));
880        assert_err!(BOOL.parse_val("farragut"));
881        assert_err!(BOOL.parse_val(""));
882        assert_err!(BOOL.parse_val("5 s"));
883
884        assert_err!(U32.parse_val("true"));
885        assert_err!(U32.parse_val("false"));
886        assert_eq!(U32.parse_val("42"), Ok(ConfigVal::U32(42)));
887        assert_err!(U32.parse_val("66.6"));
888        assert_err!(U32.parse_val("farragut"));
889        assert_err!(U32.parse_val(""));
890        assert_err!(U32.parse_val("5 s"));
891
892        assert_err!(USIZE.parse_val("true"));
893        assert_err!(USIZE.parse_val("false"));
894        assert_eq!(USIZE.parse_val("42"), Ok(ConfigVal::Usize(42)));
895        assert_err!(USIZE.parse_val("66.6"));
896        assert_err!(USIZE.parse_val("farragut"));
897        assert_err!(USIZE.parse_val(""));
898        assert_err!(USIZE.parse_val("5 s"));
899
900        assert_err!(OPT_USIZE.parse_val("true"));
901        assert_err!(OPT_USIZE.parse_val("false"));
902        assert_eq!(OPT_USIZE.parse_val("42"), Ok(ConfigVal::OptUsize(Some(42))));
903        assert_err!(OPT_USIZE.parse_val("66.6"));
904        assert_err!(OPT_USIZE.parse_val("farragut"));
905        assert_eq!(OPT_USIZE.parse_val(""), Ok(ConfigVal::OptUsize(None)));
906        assert_err!(OPT_USIZE.parse_val("5 s"));
907
908        assert_err!(F64.parse_val("true"));
909        assert_err!(F64.parse_val("false"));
910        assert_eq!(F64.parse_val("42"), Ok(ConfigVal::F64(42.0)));
911        assert_eq!(F64.parse_val("66.6"), Ok(ConfigVal::F64(66.6)));
912        assert_err!(F64.parse_val("farragut"));
913        assert_err!(F64.parse_val(""));
914        assert_err!(F64.parse_val("5 s"));
915
916        assert_eq!(
917            STRING.parse_val("true"),
918            Ok(ConfigVal::String("true".to_string()))
919        );
920        assert_eq!(
921            STRING.parse_val("false"),
922            Ok(ConfigVal::String("false".to_string()))
923        );
924        assert_eq!(
925            STRING.parse_val("66.6"),
926            Ok(ConfigVal::String("66.6".to_string()))
927        );
928        assert_eq!(
929            STRING.parse_val("42"),
930            Ok(ConfigVal::String("42".to_string()))
931        );
932        assert_eq!(
933            STRING.parse_val("farragut"),
934            Ok(ConfigVal::String("farragut".to_string()))
935        );
936        assert_eq!(STRING.parse_val(""), Ok(ConfigVal::String("".to_string())));
937        assert_eq!(
938            STRING.parse_val("5 s"),
939            Ok(ConfigVal::String("5 s".to_string()))
940        );
941
942        assert_eq!(
943            OPT_STRING.parse_val("true"),
944            Ok(ConfigVal::OptString(Some("true".to_string())))
945        );
946        assert_eq!(
947            OPT_STRING.parse_val("false"),
948            Ok(ConfigVal::OptString(Some("false".to_string())))
949        );
950        assert_eq!(
951            OPT_STRING.parse_val("66.6"),
952            Ok(ConfigVal::OptString(Some("66.6".to_string())))
953        );
954        assert_eq!(
955            OPT_STRING.parse_val("42"),
956            Ok(ConfigVal::OptString(Some("42".to_string())))
957        );
958        assert_eq!(
959            OPT_STRING.parse_val("farragut"),
960            Ok(ConfigVal::OptString(Some("farragut".to_string())))
961        );
962        assert_eq!(
963            OPT_STRING.parse_val(""),
964            Ok(ConfigVal::OptString(Some("".to_string())))
965        );
966        assert_eq!(
967            OPT_STRING.parse_val("5 s"),
968            Ok(ConfigVal::OptString(Some("5 s".to_string())))
969        );
970
971        assert_err!(DURATION.parse_val("true"));
972        assert_err!(DURATION.parse_val("false"));
973        assert_err!(DURATION.parse_val("42"));
974        assert_err!(DURATION.parse_val("66.6"));
975        assert_err!(DURATION.parse_val("farragut"));
976        assert_err!(DURATION.parse_val(""));
977        assert_eq!(
978            DURATION.parse_val("5 s"),
979            Ok(ConfigVal::Duration(Duration::from_secs(5)))
980        );
981
982        assert_eq!(
983            JSON.parse_val("true"),
984            Ok(ConfigVal::Json(serde_json::json!(true)))
985        );
986        assert_eq!(
987            JSON.parse_val("false"),
988            Ok(ConfigVal::Json(serde_json::json!(false)))
989        );
990        assert_eq!(
991            JSON.parse_val("42"),
992            Ok(ConfigVal::Json(serde_json::json!(42)))
993        );
994        assert_eq!(
995            JSON.parse_val("66.6"),
996            Ok(ConfigVal::Json(serde_json::json!(66.6)))
997        );
998        assert_err!(JSON.parse_val("farragut"));
999        assert_err!(JSON.parse_val(""));
1000        assert_err!(JSON.parse_val("5 s"));
1001        assert_eq!(
1002            JSON.parse_val("{\"joe\": \"developer\"}"),
1003            Ok(ConfigVal::Json(serde_json::json!({"joe": "developer"})))
1004        );
1005    }
1006}