1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use zng_clone_move::clmv;
use zng_var::MergeVarBuilder;

use super::*;

/// Represents multiple config sources that are matched by key.
///
/// When a config key is requested a closure defined for each config case in the switch
/// is called, if the closure returns a key the config case is used.
///
/// Note that the returned config variables are linked directly with the matched configs,
/// and if none matches returns from a fallback [`MemoryConfig`]. If a config is pushed after no match
/// the already returned variable will not update to link with the new config.
#[derive(Default)]
pub struct SwitchConfig {
    cfgs: Vec<SwitchCfg>,
}
impl SwitchConfig {
    /// New default empty.
    pub fn new() -> Self {
        Self::default()
    }

    /// Push a config case on the switch.
    ///
    /// The `match_key` closure will be called after the match of previous configs, if it returns `Some(key)`
    /// the key will be used on the `config` to retrieve the value variable.
    pub fn push(&mut self, match_key: impl Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync + 'static, config: impl AnyConfig) {
        self.cfgs.push(SwitchCfg {
            match_key: Box::new(match_key),
            cfg: Box::new(config),
        })
    }

    /// Push a config case matched by a key `prefix`.
    ///
    /// The `prefix` is stripped from the key before it is passed on to the `config`.
    ///
    /// Always matches the config if the prefix is empty.
    pub fn push_prefix(&mut self, prefix: impl Into<Txt>, config: impl AnyConfig) {
        let prefix = prefix.into();
        if prefix.is_empty() {
            self.push(|key| Some(key.clone()), config)
        } else {
            self.push(move |key| key.strip_prefix(prefix.as_str()).map(Txt::from_str), config)
        }
    }

    /// Push the config and return.
    ///
    /// See [`push`] for more details.
    ///
    /// [`push`]: Self::push
    pub fn with(mut self, match_key: impl Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync + 'static, config: impl AnyConfig) -> Self {
        self.push(match_key, config);
        self
    }

    /// Push the config and return.
    ///
    /// See [`push_prefix`] for more details.
    ///
    /// [`push_prefix`]: Self::push
    pub fn with_prefix(mut self, prefix: impl Into<Txt>, config: impl AnyConfig) -> Self {
        self.push_prefix(prefix, config);
        self
    }

    fn cfg_mut(&mut self, key: &ConfigKey) -> Option<(ConfigKey, &mut dyn AnyConfig)> {
        for c in &mut self.cfgs {
            if let Some(key) = (c.match_key)(key) {
                return Some((key, &mut *c.cfg));
            }
        }
        None
    }
}
impl AnyConfig for SwitchConfig {
    fn status(&self) -> BoxedVar<ConfigStatus> {
        let mut s = MergeVarBuilder::with_capacity(self.cfgs.len());
        for c in &self.cfgs {
            s.push(c.cfg.status());
        }
        s.build(|status| ConfigStatus::merge_status(status.iter().cloned())).boxed()
    }

    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool, shared: bool) -> BoxedVar<RawConfigValue> {
        match self.cfg_mut(&key) {
            Some((key, cfg)) => cfg.get_raw(key, default, insert, shared),
            None => LocalVar(default).boxed(),
        }
    }

    fn contains_key(&mut self, key: ConfigKey) -> BoxedVar<bool> {
        match self.cfg_mut(&key) {
            Some((key, cfg)) => cfg.contains_key(key),
            None => LocalVar(false).boxed(),
        }
    }

    fn remove(&mut self, key: &ConfigKey) -> bool {
        match self.cfg_mut(key) {
            Some((key, cfg)) => cfg.remove(&key),
            None => false,
        }
    }

    fn low_memory(&mut self) {
        for c in &mut self.cfgs {
            c.cfg.low_memory();
        }
    }
}
impl Config for SwitchConfig {
    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> BoxedVar<T> {
        let key = key.into();
        match self.cfg_mut(&key) {
            Some((key, cfg)) => {
                let source_var = cfg.get_raw(
                    key.clone(),
                    RawConfigValue::serialize(&default).unwrap_or_else(|e| panic!("invalid default value, {e}")),
                    insert,
                    false,
                );
                let var = var(RawConfigValue::deserialize(source_var.get()).unwrap_or(default));

                source_var
                    .bind_filter_map_bidi(
                        &var,
                        // Raw -> T
                        clmv!(key, |raw| {
                            match RawConfigValue::deserialize(raw.clone()) {
                                Ok(value) => Some(value),
                                Err(e) => {
                                    tracing::error!("switch config get({key:?}) error, {e:?}");
                                    None
                                }
                            }
                        }),
                        // T -> Raw
                        clmv!(key, source_var, |value| {
                            let _strong_ref = &source_var;

                            match RawConfigValue::serialize(value) {
                                Ok(raw) => Some(raw),
                                Err(e) => {
                                    tracing::error!("switch config set({key:?}) error, {e:?}");
                                    None
                                }
                            }
                        }),
                    )
                    .perm();

                var.boxed()
            }
            None => LocalVar(default).boxed(),
        }
    }
}

struct SwitchCfg {
    match_key: Box<dyn Fn(&ConfigKey) -> Option<ConfigKey> + Send + Sync>,
    cfg: Box<dyn AnyConfig>,
}