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