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