zng_ext_config/
lib.rs

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