zng_ext_config/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Config service and sources.
5//!
6//! # Services
7//!
8//! Services this extension provides.
9//!
10//! * [`CONFIG`]
11//!
12//! # Crate
13//!
14#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
15// suppress nag about very simple boxed closure signatures.
16#![expect(clippy::type_complexity)]
17#![warn(unused_extern_crates)]
18#![warn(missing_docs)]
19
20mod serde_value;
21
22mod fallback;
23pub use fallback::*;
24
25mod swap;
26pub use swap::*;
27
28mod switch;
29pub use switch::*;
30
31mod sync;
32pub use sync::*;
33
34#[cfg(feature = "json")]
35mod json;
36#[cfg(feature = "json")]
37pub use json::*;
38
39#[cfg(feature = "toml")]
40mod toml;
41#[cfg(feature = "toml")]
42pub use self::toml::*;
43
44#[cfg(feature = "ron")]
45mod ron;
46#[cfg(feature = "ron")]
47pub use self::ron::*;
48
49#[cfg(feature = "yaml")]
50mod yaml;
51#[cfg(feature = "yaml")]
52pub use self::yaml::*;
53
54pub mod settings;
55
56use std::{
57    any::Any,
58    collections::{HashMap, hash_map},
59    fmt, io,
60    sync::Arc,
61};
62
63use zng_app::view_process::raw_events::LOW_MEMORY_EVENT;
64use zng_app_context::app_local;
65use zng_clone_move::clmv;
66use zng_ext_fs_watcher::{WatchFile, WatcherReadStatus, WatcherSyncStatus, WriteFile};
67use zng_task as task;
68use zng_txt::Txt;
69use zng_var::{Var, VarHandles, VarValue, WeakVar, const_var, var};
70
71/// Represents the app main config.
72///
73/// Config sources must be loaded using [`CONFIG.load`], otherwise the config only lives for the
74/// duration of the app instance.
75///
76/// [`CONFIG.load`]: CONFIG::load
77pub struct CONFIG;
78impl CONFIG {
79    /// Replace the config source.
80    ///
81    /// Variables and bindings survive source replacement, updating to the new value or setting the new source
82    /// if the key is not present in the new source.
83    pub fn load(&self, source: impl AnyConfig) {
84        CONFIG_SV.write().load(source)
85    }
86
87    /// Gets a read-only variable that represents the IO status of the config.
88    pub fn status(&self) -> Var<ConfigStatus> {
89        CONFIG_SV.read().status()
90    }
91
92    /// Wait until [`status`] is idle (not loading nor saving).
93    ///
94    /// [`status`]: Self::status
95    pub async fn wait_idle(&self) {
96        task::yield_now().await; // in case a `load` request was just made
97        self.status().wait_match(|s| s.is_idle()).await;
98    }
99
100    /// Gets a variable that is bound to the config `key`.
101    ///
102    /// The same variable is returned for multiple requests of the same key. If the loaded config is not read-only the
103    /// returned variable can be set to update the config source.
104    ///
105    /// The `default` value is used if the key is not found in the config, the default value
106    /// is not inserted in the config, the key is inserted or replaced only when the returned variable updates.
107    pub fn get<T: ConfigValue>(&self, key: impl Into<ConfigKey>, default: T) -> Var<T> {
108        CONFIG_SV.write().get(key.into(), default, false)
109    }
110
111    /// Gets a variable that is bound to the config `key`, the `value` is set and if the `key` was
112    /// not present it is also inserted on the config.
113    pub fn insert<T: ConfigValue>(&self, key: impl Into<ConfigKey>, value: T) -> Var<T> {
114        CONFIG_SV.write().get(key.into(), value, true)
115    }
116}
117impl AnyConfig for CONFIG {
118    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool) -> Var<RawConfigValue> {
119        CONFIG_SV.write().get_raw(key, default, insert)
120    }
121
122    fn contains_key(&mut self, key: ConfigKey) -> Var<bool> {
123        CONFIG_SV.write().contains_key(key)
124    }
125
126    fn status(&self) -> Var<ConfigStatus> {
127        CONFIG.status()
128    }
129
130    fn remove(&mut self, key: &ConfigKey) -> bool {
131        CONFIG_SV.write().remove(key)
132    }
133
134    fn low_memory(&mut self) {
135        CONFIG_SV.write().low_memory()
136    }
137}
138
139app_local! {
140    static CONFIG_SV: SwapConfig = {
141        hooks();
142        SwapConfig::new()
143    };
144}
145fn hooks() {
146    LOW_MEMORY_EVENT
147        .hook(|_| {
148            CONFIG_SV.write().low_memory();
149            true
150        })
151        .perm();
152}
153
154/// Unique key to a config entry.
155pub type ConfigKey = Txt;
156
157/// Marker trait for types that can stored in a [`Config`].
158///
159/// This trait is already implemented for types it applies.
160#[diagnostic::on_unimplemented(note = "`ConfigValue` is implemented for all `T: VarValue + Serialize + DeserializeOwned`")]
161pub trait ConfigValue: VarValue + serde::Serialize + serde::de::DeserializeOwned {}
162impl<T: VarValue + serde::Serialize + serde::de::DeserializeOwned> ConfigValue for T {}
163
164/// Represents any entry type in a config.
165#[repr(transparent)]
166#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
167#[serde(transparent)]
168pub struct RawConfigValue(pub serde_value::Value);
169impl RawConfigValue {
170    /// Serialize to the raw config format.
171    pub fn serialize<T: serde::Serialize>(value: T) -> Result<Self, serde_value::SerializerError> {
172        serde_value::to_value(value).map(Self)
173    }
174
175    /// Deserialize from the raw config format.
176    pub fn deserialize<T: serde::de::DeserializeOwned>(self) -> Result<T, serde_value::DeserializerError> {
177        T::deserialize(self.0)
178    }
179}
180
181/// Represents one or more config sources behind a dynamic reference.
182///
183/// See [`Config`] for the full trait.
184pub trait AnyConfig: Send + Any {
185    /// Gets a read-only variable that represents the IO status of the config.
186    fn status(&self) -> Var<ConfigStatus>;
187
188    /// Gets a weak typed variable to the config `key`.
189    ///
190    /// This method is used when `T` cannot be passed because the config is behind a dynamic reference,
191    /// the backend must convert the value from the in memory representation to [`RawConfigValue`].
192    ///
193    /// The `default` value is used if the key is not found in the config, the default value
194    /// is only inserted in the config if `insert`, otherwise the key is inserted or replaced only when the returned variable changes.
195    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, insert: bool) -> Var<RawConfigValue>;
196
197    /// Gets a read-only variable that tracks if an entry for the `key` is in the backing storage.
198    fn contains_key(&mut self, key: ConfigKey) -> Var<bool>;
199
200    /// Removes the `key` from the backing storage.
201    ///
202    /// Any active config variable for the key will continue to work normally, retaining the last config value and
203    /// re-inserting the key if assigned a new value.
204    ///
205    /// Returns `true` if the key was found and will be removed in the next app update.
206    /// Returns `false` if the key was not found or the config is read-only.
207    fn remove(&mut self, key: &ConfigKey) -> bool;
208
209    /// Cleanup and flush RAM caches.
210    fn low_memory(&mut self);
211}
212
213/// Represents one or more config sources.
214///
215/// This trait is already implemented for all [`AnyConfig`] implementers.
216pub trait Config: AnyConfig {
217    /// Gets a variable that is bound to the config `key`.
218    ///
219    /// The same variable is returned for multiple requests of the same key. If the loaded config is not read-only the
220    /// returned variable can be set to update the config source.
221    ///
222    /// The `default` value is used if the key is not found in the config, the default value
223    /// is only inserted in the config if `insert`, otherwise the key is inserted or replaced only when the returned variable changes.
224    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> Var<T>;
225}
226impl<C: AnyConfig> Config for C {
227    fn get<T: ConfigValue>(&mut self, key: impl Into<ConfigKey>, default: T, insert: bool) -> Var<T> {
228        get_impl(self, insert, key.into(), default)
229    }
230}
231fn get_impl<T: ConfigValue, C: AnyConfig>(source: &mut C, insert: bool, key: ConfigKey, default: T) -> Var<T> {
232    source
233        .get_raw(key, RawConfigValue::serialize(&default).unwrap(), insert)
234        .filter_map_bidi(
235            move |raw| match raw.clone().deserialize() {
236                Ok(v) => Some(v),
237                Err(e) => {
238                    #[cfg(debug_assertions)]
239                    tracing::error!(
240                        "failed to get config as `{}`, raw value was {:?}, {e}",
241                        std::any::type_name::<T>(),
242                        raw
243                    );
244                    #[cfg(not(debug_assertions))]
245                    tracing::error!("failed to get config, {e}");
246                    None
247                }
248            },
249            |v| match RawConfigValue::serialize(v) {
250                Ok(v) => Some(v),
251                Err(e) => {
252                    tracing::error!("failed to set config, {e}");
253                    None
254                }
255            },
256            move || default.clone(),
257        )
258}
259
260/// Config wrapper that only provides read-only variables from the inner config.
261///
262/// Note that you can use [`SyncConfig::read`] to open a file read-only, that is more efficient than
263/// wrapping a [`SyncConfig::sync`] with this adapter.
264pub struct ReadOnlyConfig<C: Config> {
265    cfg: C,
266}
267impl<C: Config> ReadOnlyConfig<C> {
268    /// New reading from `cfg`.
269    pub fn new(cfg: C) -> Self {
270        Self { cfg }
271    }
272}
273impl<C: Config> AnyConfig for ReadOnlyConfig<C> {
274    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, _: bool) -> Var<RawConfigValue> {
275        self.cfg.get_raw(key, default, false).read_only()
276    }
277
278    fn contains_key(&mut self, key: ConfigKey) -> Var<bool> {
279        self.cfg.contains_key(key)
280    }
281
282    fn status(&self) -> Var<ConfigStatus> {
283        self.cfg.status()
284    }
285
286    fn remove(&mut self, _key: &ConfigKey) -> bool {
287        false
288    }
289
290    fn low_memory(&mut self) {
291        self.cfg.low_memory()
292    }
293}
294
295/// Memory only config.
296///
297/// Values are retained in memory even if all variables to the key are dropped, but they are lost when the process ends.
298#[derive(Default)]
299pub struct MemoryConfig {
300    values: HashMap<ConfigKey, Var<RawConfigValue>>,
301    contains: HashMap<ConfigKey, WeakVar<bool>>,
302}
303
304impl AnyConfig for MemoryConfig {
305    fn status(&self) -> Var<ConfigStatus> {
306        const_var(ConfigStatus::Loaded)
307    }
308
309    fn get_raw(&mut self, key: ConfigKey, default: RawConfigValue, _insert: bool) -> Var<RawConfigValue> {
310        match self.values.entry(key) {
311            hash_map::Entry::Occupied(e) => e.get().clone(),
312            hash_map::Entry::Vacant(e) => {
313                let r = var(default);
314
315                if let Some(v) = self.contains.get(e.key())
316                    && let Some(v) = v.upgrade()
317                {
318                    v.set(true);
319                }
320
321                e.insert(r).clone()
322            }
323        }
324    }
325
326    fn contains_key(&mut self, key: ConfigKey) -> Var<bool> {
327        match self.contains.entry(key) {
328            hash_map::Entry::Occupied(mut e) => {
329                if let Some(r) = e.get().upgrade() {
330                    r
331                } else {
332                    let r = var(self.values.contains_key(e.key()));
333                    e.insert(r.downgrade());
334                    r
335                }
336            }
337            hash_map::Entry::Vacant(e) => {
338                let r = var(self.values.contains_key(e.key()));
339                e.insert(r.downgrade());
340                r
341            }
342        }
343    }
344
345    fn remove(&mut self, key: &ConfigKey) -> bool {
346        if self.values.remove(key).is_some() {
347            self.contains.retain(|_, v| v.strong_count() > 0);
348
349            if let Some(v) = self.contains.get(key)
350                && let Some(v) = v.upgrade()
351            {
352                v.set(false);
353            }
354            true
355        } else {
356            false
357        }
358    }
359
360    fn low_memory(&mut self) {
361        self.contains.retain(|_, v| v.strong_count() > 0);
362    }
363}
364
365struct ConfigVar<T: ConfigValue> {
366    var: WeakVar<T>,
367    binding: VarHandles,
368}
369impl<T: ConfigValue> ConfigVar<T> {
370    fn new_any(var: WeakVar<T>, binding: VarHandles) -> Box<dyn AnyConfigVar> {
371        Box::new(Self { var, binding })
372    }
373}
374struct ConfigContainsVar {
375    var: WeakVar<bool>,
376    binding: VarHandles,
377}
378
379/// Map of configs already bound to a variable.
380///
381/// The map only holds a weak reference to the variables.
382#[derive(Default)]
383pub struct ConfigVars {
384    values: HashMap<ConfigKey, Box<dyn AnyConfigVar>>,
385    contains: HashMap<ConfigKey, ConfigContainsVar>,
386}
387impl ConfigVars {
388    /// Gets the already bound variable or calls `bind` to generate a new binding.
389    pub fn get_or_bind<T: ConfigValue>(&mut self, key: ConfigKey, bind: impl FnOnce(&ConfigKey) -> Var<T>) -> Var<T> {
390        match self.values.entry(key) {
391            hash_map::Entry::Occupied(mut e) => {
392                if e.get().can_upgrade() {
393                    if let Some(x) = e.get().as_any().downcast_ref::<ConfigVar<T>>() {
394                        if let Some(var) = x.var.upgrade() {
395                            return var;
396                        }
397                    } else {
398                        tracing::error!(
399                            "cannot get key `{}` as `{}` because it is already requested with a different type",
400                            e.key(),
401                            std::any::type_name::<T>()
402                        );
403                        return bind(e.key());
404                    }
405                }
406                // cannot upgrade
407                let cfg = bind(e.key());
408
409                let res = var(cfg.get());
410                let binding = res.bind_map_bidi(
411                    &cfg,
412                    clmv!(cfg, |v| {
413                        let _strong_ref = &cfg;
414                        v.clone()
415                    }),
416                    Clone::clone,
417                );
418
419                e.insert(ConfigVar::new_any(res.downgrade(), binding));
420                res
421            }
422            hash_map::Entry::Vacant(e) => {
423                let cfg = bind(e.key());
424                let res = var(cfg.get());
425                let binding = res.bind_map_bidi(
426                    &cfg,
427                    clmv!(cfg, |v| {
428                        let _strong_ref = &cfg;
429                        v.clone()
430                    }),
431                    Clone::clone,
432                );
433
434                e.insert(ConfigVar::new_any(res.downgrade(), binding));
435                res
436            }
437        }
438    }
439
440    /// Bind the contains variable.
441    pub fn get_or_bind_contains(&mut self, key: ConfigKey, bind: impl FnOnce(&ConfigKey) -> Var<bool>) -> Var<bool> {
442        match self.contains.entry(key) {
443            hash_map::Entry::Occupied(mut e) => {
444                if let Some(res) = e.get().var.upgrade() {
445                    return res;
446                }
447
448                let cfg = bind(e.key());
449                let res = var(cfg.get());
450
451                let binding = VarHandles::from([
452                    cfg.bind(&res),
453                    res.hook(move |_| {
454                        let _strong_ref = &cfg;
455                        true
456                    }),
457                ]);
458
459                e.insert(ConfigContainsVar {
460                    var: res.downgrade(),
461                    binding,
462                });
463
464                res
465            }
466            hash_map::Entry::Vacant(e) => {
467                let cfg = bind(e.key());
468                let res = var(cfg.get());
469
470                let binding = VarHandles::from([
471                    cfg.bind(&res),
472                    res.hook(move |_| {
473                        let _strong_ref = &cfg;
474                        true
475                    }),
476                ]);
477
478                e.insert(ConfigContainsVar {
479                    var: res.downgrade(),
480                    binding,
481                });
482
483                res
484            }
485        }
486    }
487
488    /// Bind all variables to the new `source`.
489    ///
490    /// If the map entry is present in the `source` the variable is updated to the new value, if not the entry
491    /// is inserted in the source. The variable is then bound to the source.
492    pub fn rebind(&mut self, source: &mut dyn AnyConfig) {
493        self.values.retain(|key, wk_var| wk_var.rebind(key, source));
494        self.contains.retain(|key, wk_var| wk_var.rebind(key, source));
495    }
496
497    /// System warning low memory, flush caches.
498    pub fn low_memory(&mut self) {
499        self.values.retain(|_, v| v.can_upgrade());
500        self.contains.retain(|_, v| v.var.strong_count() > 0)
501    }
502}
503trait AnyConfigVar: Any + Send + Sync {
504    fn as_any(&self) -> &dyn Any;
505    fn can_upgrade(&self) -> bool;
506    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool;
507}
508impl<T: ConfigValue> AnyConfigVar for ConfigVar<T> {
509    fn as_any(&self) -> &dyn Any {
510        self
511    }
512
513    fn can_upgrade(&self) -> bool {
514        self.var.strong_count() > 0
515    }
516
517    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool {
518        let var = if let Some(var) = self.var.upgrade() {
519            var
520        } else {
521            // no need to retain, will bind directly to new source if requested later.
522            return false;
523        };
524
525        // get or insert the source var
526        let source_var = source.get_raw(key.clone(), RawConfigValue::serialize(var.get()).unwrap(), false);
527
528        // var.set_from_map(source_var)
529        var.modify(clmv!(source_var, key, |vm| {
530            match RawConfigValue::deserialize::<T>(source_var.get()) {
531                Ok(value) => {
532                    vm.set(value);
533                }
534                Err(e) => {
535                    // invalid data error
536                    tracing::error!("rebind config get({key:?}) error, {e:?}");
537
538                    // try to override
539                    source_var.set(RawConfigValue::serialize(vm.value()).unwrap());
540                }
541            }
542        }));
543
544        let mut first = true;
545        self.binding = source_var.bind_filter_map_bidi(
546            &var,
547            // Raw -> T
548            clmv!(key, |raw| {
549                match RawConfigValue::deserialize(raw.clone()) {
550                    Ok(value) => Some(value),
551                    Err(e) => {
552                        tracing::error!("rebind config get({key:?}) error, {e:?}");
553                        None
554                    }
555                }
556            }),
557            // T -> Raw
558            clmv!(key, source_var, |value| {
559                if std::mem::take(&mut first) {
560                    return None; // skip value we just set.
561                }
562
563                let _strong_ref = &source_var;
564                match RawConfigValue::serialize(value) {
565                    Ok(raw) => Some(raw),
566                    Err(e) => {
567                        tracing::error!("rebind config set({key:?}) error, {e:?}");
568                        None
569                    }
570                }
571            }),
572        );
573
574        true
575    }
576}
577impl ConfigContainsVar {
578    fn rebind(&mut self, key: &ConfigKey, source: &mut dyn AnyConfig) -> bool {
579        if let Some(res) = self.var.upgrade() {
580            let cfg = source.contains_key(key.clone());
581            res.set_from(&cfg);
582
583            self.binding = VarHandles::from([
584                cfg.bind(&res),
585                res.hook(move |_| {
586                    let _strong_ref = &cfg;
587                    true
588                }),
589            ]);
590
591            true
592        } else {
593            false
594        }
595    }
596}
597
598/// Represents the current IO status of the config.
599#[derive(Debug, Clone)]
600pub enum ConfigStatus {
601    /// Config is loaded.
602    Loaded,
603    /// Config is loading.
604    Loading,
605    /// Config is saving.
606    Saving,
607    /// Config last load failed.
608    LoadErrors(ConfigStatusError),
609    /// Config last save failed.
610    SaveErrors(ConfigStatusError),
611}
612impl ConfigStatus {
613    /// If status is not loading nor saving.
614    pub fn is_idle(&self) -> bool {
615        !matches!(self, Self::Loading | Self::Saving)
616    }
617
618    /// If status is load or save errors.
619    pub fn is_err(&self) -> bool {
620        matches!(self, ConfigStatus::LoadErrors(_) | ConfigStatus::SaveErrors(_))
621    }
622
623    /// Errors list.
624    ///
625    /// Note that [`is_err`] may be true even when this is empty.
626    ///
627    /// [`is_err`]: Self::is_err
628    pub fn errors(&self) -> &[Arc<dyn std::error::Error + Send + Sync>] {
629        match self {
630            ConfigStatus::LoadErrors(e) => e,
631            ConfigStatus::SaveErrors(e) => e,
632            _ => &[],
633        }
634    }
635
636    /// merge all `status`.
637    pub fn merge_status(status: impl Iterator<Item = ConfigStatus>) -> ConfigStatus {
638        let mut load_errors = vec![];
639        let mut save_errors = vec![];
640        let mut loading = false;
641        let mut saving = false;
642        for s in status {
643            match s {
644                ConfigStatus::Loaded => {}
645                ConfigStatus::Loading => loading = true,
646                ConfigStatus::Saving => saving = true,
647                ConfigStatus::LoadErrors(e) => {
648                    if load_errors.is_empty() {
649                        load_errors = e;
650                    } else {
651                        load_errors.extend(e);
652                    }
653                }
654                ConfigStatus::SaveErrors(e) => {
655                    if save_errors.is_empty() {
656                        save_errors = e;
657                    } else {
658                        save_errors.extend(e);
659                    }
660                }
661            }
662        }
663
664        if loading {
665            ConfigStatus::Loading
666        } else if saving {
667            ConfigStatus::Saving
668        } else if !load_errors.is_empty() {
669            ConfigStatus::LoadErrors(load_errors)
670        } else if !save_errors.is_empty() {
671            ConfigStatus::SaveErrors(save_errors)
672        } else {
673            ConfigStatus::Loaded
674        }
675    }
676}
677impl fmt::Display for ConfigStatus {
678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679        match self {
680            Self::Loaded => Ok(()),
681            Self::Loading => write!(f, "loading…"),
682            Self::Saving => write!(f, "saving…"),
683            Self::LoadErrors(e) => {
684                writeln!(f, "read errors:")?;
685                for e in e {
686                    writeln!(f, "   {e}")?;
687                }
688                Ok(())
689            }
690            Self::SaveErrors(e) => {
691                writeln!(f, "write errors:")?;
692                for e in e {
693                    writeln!(f, "   {e}")?;
694                }
695                Ok(())
696            }
697        }
698    }
699}
700impl PartialEq for ConfigStatus {
701    fn eq(&self, other: &Self) -> bool {
702        match (self, other) {
703            (Self::LoadErrors(a), Self::LoadErrors(b)) => a.is_empty() && b.is_empty(),
704            (Self::SaveErrors(a), Self::SaveErrors(b)) => a.is_empty() && b.is_empty(),
705            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
706        }
707    }
708}
709impl Eq for ConfigStatus {}
710impl WatcherSyncStatus<ConfigStatusError, ConfigStatusError> for ConfigStatus {
711    fn writing() -> Self {
712        ConfigStatus::Saving
713    }
714
715    fn write_error(e: ConfigStatusError) -> Self {
716        ConfigStatus::SaveErrors(e)
717    }
718}
719impl WatcherReadStatus<ConfigStatusError> for ConfigStatus {
720    fn idle() -> Self {
721        ConfigStatus::Loaded
722    }
723
724    fn reading() -> Self {
725        ConfigStatus::Loading
726    }
727
728    fn read_error(e: ConfigStatusError) -> Self {
729        ConfigStatus::LoadErrors(e)
730    }
731}
732type ConfigStatusError = Vec<Arc<dyn std::error::Error + Send + Sync>>;