zng_ext_config/
switch.rs

1use zng_clone_move::clmv;
2use zng_var::MergeVarBuilder;
3
4use super::*;
5
6/// Represents multiple config sources that are matched by key.
7///
8/// When a config key is requested a closure defined for each config case in the switch
9/// is called, if the closure returns a key the config case is used.
10///
11/// Note that the returned config variables are linked directly with the matched configs,
12/// and if none matches returns from a fallback [`MemoryConfig`]. If a config is pushed after no match
13/// the already returned variable will not update to link with the new config.
14#[derive(Default)]
15pub struct SwitchConfig {
16    cfgs: Vec<SwitchCfg>,
17}
18impl SwitchConfig {
19    /// New default empty.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Push a config case on the switch.
25    ///
26    /// The `match_key` closure will be called after the match of previous configs, if it returns `Some(key)`
27    /// the key will be used on the `config` to retrieve the value variable.
28    pub fn push(&mut self, match_key: impl Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync + 'static, config: impl AnyConfig) {
29        self.cfgs.push(SwitchCfg {
30            match_key: Box::new(match_key),
31            cfg: Box::new(config),
32        })
33    }
34
35    /// Push a config case matched by a key `prefix`.
36    ///
37    /// The `prefix` is stripped from the key before it is passed on to the `config`.
38    ///
39    /// Always matches the config if the prefix is empty.
40    pub fn push_prefix(&mut self, prefix: impl Into<Txt>, config: impl AnyConfig) {
41        let prefix = prefix.into();
42        if prefix.is_empty() {
43            self.push(|key| Some(key.clone()), config)
44        } else {
45            self.push(move |key| key.strip_prefix(prefix.as_str()).map(Txt::from_str), config)
46        }
47    }
48
49    /// Push the config and return.
50    ///
51    /// See [`push`] for more details.
52    ///
53    /// [`push`]: Self::push
54    pub fn with(mut self, match_key: impl Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync + 'static, config: impl AnyConfig) -> Self {
55        self.push(match_key, config);
56        self
57    }
58
59    /// Push the config and return.
60    ///
61    /// See [`push_prefix`] for more details.
62    ///
63    /// [`push_prefix`]: Self::push
64    pub fn with_prefix(mut self, prefix: impl Into<Txt>, config: impl AnyConfig) -> Self {
65        self.push_prefix(prefix, config);
66        self
67    }
68
69    fn cfg_mut(&mut self, key: &ConfigKey) -> Option<(ConfigKey, &mut dyn AnyConfig)> {
70        for c in &mut self.cfgs {
71            if let Some(key) = (c.match_key)(key) {
72                return Some((key, &mut *c.cfg));
73            }
74        }
75        None
76    }
77}
78impl AnyConfig for SwitchConfig {
79    fn status(&self) -> BoxedVar<ConfigStatus> {
80        let mut s = MergeVarBuilder::with_capacity(self.cfgs.len());
81        for c in &self.cfgs {
82            s.push(c.cfg.status());
83        }
84        s.build(|status| ConfigStatus::merge_status(status.iter().cloned())).boxed()
85    }
86
87    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool, shared: bool) -> BoxedVar<RawConfigValue> {
88        match self.cfg_mut(&key) {
89            Some((key, cfg)) => cfg.get_raw(key, default, insert, shared),
90            None => LocalVar(default).boxed(),
91        }
92    }
93
94    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
95        match self.cfg_mut(&key) {
96            Some((key, cfg)) => cfg.contains_key(key),
97            None => LocalVar(false).boxed(),
98        }
99    }
100
101    fn remove(&mut self, key: &ConfigKey) -> bool {
102        match self.cfg_mut(key) {
103            Some((key, cfg)) => cfg.remove(&key),
104            None => false,
105        }
106    }
107
108    fn low_memory(&mut self) {
109        for c in &mut self.cfgs {
110            c.cfg.low_memory();
111        }
112    }
113}
114impl Config for SwitchConfig {
115    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T> {
116        let key = key.into();
117        match self.cfg_mut(&key) {
118            Some((key, cfg)) => {
119                let source_var = cfg.get_raw(
120                    key.clone(),
121                    RawConfigValue::serialize(&default).unwrap_or_else(|e| panic!("invalid default value, {e}")),
122                    insert,
123                    false,
124                );
125                let var = var(RawConfigValue::deserialize(source_var.get()).unwrap_or(default));
126
127                source_var
128                    .bind_filter_map_bidi(
129                        &var,
130                        // Raw -> T
131                        clmv!(key, |raw| {
132                            match RawConfigValue::deserialize(raw.clone()) {
133                                Ok(value) => Some(value),
134                                Err(e) => {
135                                    tracing::error!("switch config get({key:?}) error, {e:?}");
136                                    None
137                                }
138                            }
139                        }),
140                        // T -> Raw
141                        clmv!(key, source_var, |value| {
142                            let _strong_ref = &source_var;
143
144                            match RawConfigValue::serialize(value) {
145                                Ok(raw) => Some(raw),
146                                Err(e) => {
147                                    tracing::error!("switch config set({key:?}) error, {e:?}");
148                                    None
149                                }
150                            }
151                        }),
152                    )
153                    .perm();
154
155                var.boxed()
156            }
157            None => LocalVar(default).boxed(),
158        }
159    }
160}
161
162struct SwitchCfg {
163    match_key: Box<dyn Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync>,
164    cfg: Box<dyn AnyConfig>,
165}