metre/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! # **metre**. The configuration loader for Rust.
3//!   
4//! #### AKA: The `#[derive(Config)]` macro 
5//! 
6//! **metre** is a configuration loader for Rust that allows you to load configurations from a variety of formats such as **toml**, **json**, **jsonc** and **yaml**
7//! It also supports a variety of sources such as **program defaults**, **env variables**, **files**, and **urls**.   
8//!  
9//!   
10//! ## Focus
11//! 
12//! **metre** focus is to provide a **declarative** and **type-safe** way to load configurations in Rust.
13//!  
14//!  
15//! ## How?
16//!
17//! **metre** works by defining a struct that implements the `Config` trait, usually via the `#[derive(Config)]` macro. 
18//! 
19//! Under the hood metre creates deep partial version of the struct to accumulate the configuration from different sources.
20//!
21//! Once all the configuration is accumulated, you can access the final configuration as the defined struct. If the sum of all sources does not comply with the required properties, metre will return an error.
22//!  
23//!  
24//! ## Show me the code
25//! 
26//! The following code shows how to load configurations 
27//! from different sources and in different formats 
28//! with descriptions for most macro attributes 
29//! 
30//! To see all macro attributes available see the [Config](macro@Config) derive macro documentation.
31//! 
32//! ```
33//! # fn load_config() -> Result<(), Box<dyn std::error::Error>> {
34//! use metre::{Config, ConfigLoader, Format};
35//! 
36//! #[derive(Config)]
37//! struct MyConfig {
38//!   #[config(default = 3000)]
39//!   port: u16,
40//!   foo: String,
41//! }
42//! 
43//! let mut loader = ConfigLoader::<MyConfig>::new();
44//!
45//! loader.defaults()?;
46//! loader.file("config.json", Format::Json)?;
47//! loader.env()?;
48//!
49//! // config have the type MyConfig here, or loader.finish() returns an error
50//! let config = loader.finish()?;
51//! 
52//! # Ok(())
53//! # }
54//! ``` 
55
56use owo_colors::*;
57use serde::de::DeserializeOwned;
58use std::fmt::Display;
59use std::path::Path;
60use std::sync::Arc;
61#[cfg(feature = "env")]
62use std::{env::VarError, collections::{BTreeMap, HashMap}};
63#[allow(unused)]
64use std::convert::Infallible;
65
66pub mod error;
67pub mod merge;
68pub mod parse;
69#[doc(hidden)]
70pub mod util;
71
72pub use error::Error;
73/// Derive macro for [`Config`] trait
74///
75/// This macro will implement the [`Config`] trait for the given struct
76/// and define the associated [`PartialConfig`] struct
77///
78/// The generated [`PartialConfig`] struct will have the name `Partial{StructName}` by default
79/// and is accesible through the `Partial` associated type of the generated [`Config`] trait
80///
81/// The generated [`PartialConfig`] will have the same visibility as the [`Config`] (eg: pub, pub(crate), private, etc).
82///
83/// The [`PartialConfig`] generated type is a deep-partial version of the struct
84///
85/// See the [`Config`] and [`PartialConfig`] documentation for more information on the available methods.
86///
87/// # Container Attributes
88/// | Attribute | Description | Default | Example | Observations |
89/// | --- | --- | --- | --- | --- |
90/// | rename_all | The case conversion to apply to all fields | none | `#[config(rename_all = "snake_case")]` | This will apply `#[serde(rename_all)]` to the PartialConfig struct |
91/// | skip_env | If applied, this struct will not load anything from env variables | false | `#[config(skip_env)]` |
92/// | env_prefix | The prefix to use for all fields environment variables | "{}" | `#[config(env_prefix = "{}MY_APP_")]` | Almost always you'll want to include the `{}` placeholder like `"{}MY_APP"` to allow auto generated prefixes to work, if not the env key will be fixed to the value of the attribute |
93/// | allow_unknown_fields | Allow unknown fields in deserialization of the PartialConfig type | false | `#[config(allow_unknown_fields)]` | By default metre will add a `#[serde(deny_unknown_fields)]` to the Partial definition, use this attribute if you want to override this behavior |
94/// | parial_name | The name of the generated PartialConfig struct | `Partial{StructName}` | `#[config(partial_name = PartialMyConfig)] | rename the PartialConfig generated struct, the PartialConfig struct will have the same visibility as the struct |
95/// | crate | Rename the metre crate in the generated derive code | `metre` | `#[config(crate = other)]` | This is almost only useful for internal unit tests |
96///
97/// # Field Attributes
98/// | Attribute | Description | Default | Example | Observations |
99/// | --- | --- | --- | --- | --- |
100/// | env | The name of the environment variable to use for this field | `"{}PROPERTY_NAME"` | `#[config(env = "{}PORT")]` | The default value of the attribute is the SCREAMING_SNAKE_CASE version of the field name after applying rename and rename_all configurations, and the `{}` placeholder is filled with the auto calculated env prefix |
101/// | skip_env | If applied, this field will not load from env variables | false | `#[config(skip_env)]` | This attribute has precedence over the skip_env attribute in the container |
102/// | parse_env | The name of the function to use to parse the value from the environment variable | `FromStr::from_str` | `#[config(parse_env = parse_fn)]` | The function must have the signature `fn(&str) -> Result<Option<T>, E>` where `T` is the type of the field and `E` is any error that implements Display, see the [`parse`] module to see utility functions that can be used here |
103/// | merge | The name of the function to use to merge two values of this field | - | `#[config(merge = merge_fn)]` | The function must have the signature `fn(&mut Option<T>, Option<T>) -> Result<(), E>` where `T` is the type of the field and `E` is any error that implements Display, see the [`merge`] module to find utility functions that can be used here, the default implementation replaces the previous value with the next, if it is present in the new added stage |
104/// | default | The default value to use for this field | none | `#[config(default = 3000)]` | The default value must be of the same type as the field, if the field is an Option, the default value must be of the same type as the inner type of the Option, the [`Default::default`] implementation of the Partial struct will not use this value, to get the values defined with this attribute use [`PartialConfig::defaults`] |
105/// | flatten | If applied, this field will be merged with the previous stage instead of replacing it | false | `#[config(flatten)]` | This attribute will apply a `#[serde(flatten)]` to the PartialConfig struct, it will also modify the calculated env key prefix for nested fields |
106/// | nested | If applied, this field will be treated as a nested configuration | false | `#[config(nested)]` | This attrbute indicates that this field is a nested partial configuration, the nested field must also implement the [`Config`] trait |
107/// | rename | The rename the field in the partial configuration | - | `#[config(rename = "other_name")]` | This will apply a `#[serde(rename)]` attribute to the Partial struct, it will also modify the auto calculated env key for the field |
108#[cfg(feature = "derive")]
109#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
110pub use metre_macros::Config;
111
112use error::{FromPartialError, MergeError};
113
114#[cfg(feature = "env")]
115#[cfg_attr(docsrs, doc(cfg(feature = "env")))]
116use error::FromEnvError; 
117
118/// The Config trait that is implemented from the [`Config`](macro@Config) derive macro
119///
120/// see the [`Config`](macro@Config) derive macro for more information
121/// and the [`PartialConfig`] trait for more information on the methods available
122pub trait Config: Sized {
123  /// The Partial type generated by the [`Config`](macro@Config) derive macro
124  ///
125  /// This is a deep-partial version of the struct
126  type Partial: PartialConfig;
127
128  /// Tries to create a configuration from a partial configuration
129  ///
130  /// This will error if the partial configuration is missing required properties
131  fn from_partial(partial: Self::Partial) -> Result<Self, FromPartialError>;
132}
133
134/// The partial configuration trait that is automatically implemented by the [`Config`](macro@Config) derive macro.
135///
136/// You should almost never want to implement this trait manually.
137///
138/// Note that this trait is implemented for the [Config::Partial] associated type and not for the struct itself.
139///
140/// The [Config::Partial] associated type is a auto generated struct definition that is a deep partial version of target struct
141pub trait PartialConfig: DeserializeOwned + Default {
142  /// Get the default values for this partial configuration as defined with the `#[config(default = value)]` attributes
143  ///
144  /// Note that the [Default::default] implementation will differ from this method, as it will return a totally empty struct
145  fn defaults() -> Self;
146
147  /// Deep merge this partial configuration with another
148  fn merge(&mut self, other: Self) -> Result<(), MergeError>;
149
150  /// List of missing properties in this partial configuration that are required in the final configuration
151  fn list_missing_properties(&self) -> Vec<String>;
152
153  /// Returns true if this partial configuration has no values
154  fn is_empty(&self) -> bool;
155
156  /// Create a partial configuration from environment variables
157  /// [`EnvProvider`] is specially usefull for unit tests and is already implemented for several
158  /// types of [HashMap]'s and [BTreeMap]'s from the standard library
159  #[cfg(feature = "env")]
160  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
161  fn from_env_with_provider_and_optional_prefix<E: EnvProvider>(
162    env: &E,
163    prefix: Option<&str>,
164  ) -> Result<Self, FromEnvError>;
165
166  /// Forwards to [`Self::from_env_with_provider_and_optional_prefix`]
167  #[cfg(feature = "env")]
168  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
169  fn from_env_with_provider_and_prefix<E: EnvProvider, P: AsRef<str>>(
170    env: &E,
171    prefix: P,
172  ) -> Result<Self, FromEnvError> {
173    Self::from_env_with_provider_and_optional_prefix(env, Some(prefix.as_ref()))
174  }
175
176  /// Forwards to [`Self::from_env_with_provider_and_optional_prefix`]
177  #[cfg(feature = "env")]
178  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
179  fn from_env_with_provider<E: EnvProvider>(env: &E) -> Result<Self, FromEnvError> {
180    Self::from_env_with_provider_and_optional_prefix(env, None)
181  }
182
183  /// Forwards to [`Self::from_env_with_provider_and_optional_prefix`] with the standard library's [`std::env::var`] as the [`EnvProvider`]
184  #[cfg(feature = "env")]
185  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
186  fn from_env_with_prefix<P: AsRef<str>>(prefix: P) -> Result<Self, FromEnvError> {
187    Self::from_env_with_provider_and_optional_prefix(&StdEnv, Some(prefix.as_ref()))
188  }
189
190  /// Forwards to [`Self::from_env_with_provider_and_optional_prefix`] with the standard library's [`std::env::var`] as the [`EnvProvider`]
191  #[cfg(feature = "env")]
192  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
193  fn from_env() -> Result<Self, FromEnvError> {
194    Self::from_env_with_provider_and_optional_prefix(&StdEnv, None)
195  }
196}
197
198impl<T: Config> Config for Option<T> {
199  type Partial = Option<T::Partial>;
200  fn from_partial(partial: Self::Partial) -> Result<Self, FromPartialError> {
201    match partial {
202      None => Ok(None),
203      Some(inner) => {
204        if inner.is_empty() {
205          Ok(None)
206        } else {
207          let v = T::from_partial(inner)?;
208          Ok(Some(v))
209        }
210      }
211    }
212  }
213}
214
215impl<T: PartialConfig> PartialConfig for Option<T> {
216  fn defaults() -> Self {
217    let inner = T::defaults();
218    if inner.is_empty() {
219      None
220    } else {
221      Some(inner)
222    }
223  }
224
225  fn merge(&mut self, other: Self) -> Result<(), MergeError> {
226    match (self.as_mut(), other) {
227      (None, Some(other)) => *self = Some(other),
228      (Some(me), Some(other)) => me.merge(other)?,
229      (Some(_), None) => {}
230      (None, None) => {}
231    };
232
233    Ok(())
234  }
235
236  fn list_missing_properties(&self) -> Vec<String> {
237    match self {
238      None => vec![],
239      Some(me) => {
240        if !me.is_empty() {
241          me.list_missing_properties()
242        } else {
243          vec![]
244        }
245      }
246    }
247  }
248
249  fn is_empty(&self) -> bool {
250    match self {
251      None => true,
252      Some(me) => me.is_empty(),
253    }
254  }
255
256  #[cfg(feature = "env")]
257  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
258  fn from_env_with_provider_and_optional_prefix<E: EnvProvider>(
259    env: &E,
260    prefix: Option<&str>,
261  ) -> Result<Self, FromEnvError> {
262    let v = T::from_env_with_provider_and_optional_prefix(env, prefix)?;
263    if v.is_empty() {
264      Ok(None)
265    } else {
266      Ok(Some(v))
267    }
268  }
269}
270
271/// Implement this trait if you want to load a configuration from custom environment variables
272/// that are not in [`std::env::var`]
273///
274/// This is speecially usefull for unit tests
275///
276/// This trait is already implemented for several kinds of [HashMap]'s and [BTreeMap]'s from the standard library
277pub trait EnvProvider {
278  type Error: Display;
279  /// Read a variable from the enviroment
280  ///
281  /// This should fail if the variable is not UTF-8 encoded
282  ///
283  /// If the variable is not present, implementations should return `Ok(None)`
284  fn get(&self, key: &str) -> Result<Option<String>, Self::Error>;
285}
286
287#[cfg(feature = "env")]
288#[cfg_attr(docsrs, doc(cfg(feature = "env")))]
289macro_rules! impl_env_provider_for_map {
290  ($ty:ty) => {
291    impl EnvProvider for $ty {
292      type Error = Infallible;
293      fn get(&self, key: &str) -> Result<Option<String>, Self::Error> {
294        Ok(self.get(key).map(ToString::to_string))
295      }
296    }
297  };
298}
299
300#[cfg(feature = "env")]
301impl_env_provider_for_map!(HashMap<String, String>);
302#[cfg(feature = "env")]
303impl_env_provider_for_map!(HashMap<&str, String>);
304#[cfg(feature = "env")]
305impl_env_provider_for_map!(HashMap<String, &str>);
306#[cfg(feature = "env")]
307impl_env_provider_for_map!(HashMap<&str, &str>);
308#[cfg(feature = "env")]
309impl_env_provider_for_map!(BTreeMap<String, String>);
310#[cfg(feature = "env")]
311impl_env_provider_for_map!(BTreeMap<&str, String>);
312#[cfg(feature = "env")]
313impl_env_provider_for_map!(BTreeMap<String, &str>);
314#[cfg(feature = "env")]
315impl_env_provider_for_map!(BTreeMap<&str, &str>);
316
317/// An implementation of [`EnvProvider`] that reads from the standard library's [`std::env::var`]
318#[derive(Debug, Clone, Copy)]
319#[cfg(feature = "env")]
320#[cfg_attr(docsrs, doc(cfg(feature = "env")))]
321pub struct StdEnv;
322
323#[cfg(feature = "env")]
324#[cfg_attr(docsrs, doc(cfg(feature = "env")))]
325impl EnvProvider for StdEnv {
326  type Error = VarError;
327  fn get(&self, key: &str) -> Result<Option<String>, Self::Error> {
328    match std::env::var(key) {
329      Err(e) => match &e {
330        VarError::NotPresent => Ok(None),
331        VarError::NotUnicode(_) => Err(e),
332      },
333      Ok(v) => Ok(Some(v)),
334    }
335  }
336}
337
338/// A location from where a configuration was loaded
339///
340/// can be from Memory, File, or URL
341#[derive(Debug, Clone, Eq, PartialEq, Hash)]
342pub enum LoadLocation {
343  Memory,
344  File(String),
345  #[cfg(any(feature = "url-blocking", feature = "url-async"))]
346  #[cfg_attr(docsrs, doc(cfg(any(feature = "url-blocking", feature = "url-async"))))]
347  Url(String),
348}
349
350impl Display for LoadLocation {
351  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352    use LoadLocation::*;
353    match self {
354      Memory => write!(f, "{}", "memory".yellow()),
355      File(location) => write!(f, "file: {}", location.yellow()),
356      #[cfg(any(feature = "url-blocking", feature = "url-async"))]
357      Url(location) => write!(f, "url: {}", location.yellow()),
358    }
359  }
360}
361
362/// List of known configuration formats
363#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
364pub enum Format {
365  #[cfg(feature = "json")]
366  #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
367  Json,
368  #[cfg(feature = "jsonc")]
369  #[cfg_attr(docsrs, doc(cfg(feature = "jsonc")))]
370  Jsonc,
371  #[cfg(feature = "toml")]
372  #[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
373  Toml,
374  #[cfg(feature = "yaml")]
375  #[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
376  Yaml,
377}
378
379/// The configuration loader
380#[derive(Debug, Clone, Eq, PartialEq, Hash)]
381pub struct ConfigLoader<T: Config> {
382  partial: T::Partial,
383}
384
385impl<T: Config> ConfigLoader<T> {
386  /// Create a new configuration loader with all fields set as empty
387  pub fn new() -> Self {
388    Self {
389      partial: T::Partial::default(),
390    }
391  }
392
393  /// Add a partial configuration from a file
394  #[allow(clippy::result_large_err)]
395  pub fn file(&mut self, path: &str, format: Format) -> Result<&mut Self, Error> {
396    let code = std::fs::read_to_string(path).map_err(|e| Error::Io {
397      path: path.into(),
398      source: Arc::new(e),
399    })?;
400
401    self.code_with_location(&code, format, LoadLocation::File(path.to_string()))
402  }
403
404  /// Add a partial configuration from a file, if it exists
405  #[allow(clippy::result_large_err)]
406  pub fn file_optional(&mut self, path: &str, format: Format) -> Result<&mut Self, Error> {
407    let exists = Path::new(path).try_exists().map_err(|e| Error::Io {
408      path: path.into(),
409      source: Arc::new(e),
410    })?;
411
412    if exists {
413      self.file(path, format)
414    } else {
415      Ok(self)
416    }
417  }
418
419  /// Add a partial configuration from enviroment varialbes
420  #[cfg(feature = "env")]
421  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
422  #[inline(always)]
423  #[allow(clippy::result_large_err)]
424  pub fn env(&mut self) -> Result<&mut Self, Error> {
425    self._env(&StdEnv, None)
426  }
427
428  /// Add a partial configuration from enviroment variables with a prefix
429  #[cfg(feature = "env")]
430  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
431  #[inline(always)]
432  #[allow(clippy::result_large_err)]
433  pub fn env_with_prefix(&mut self, prefix: &str) -> Result<&mut Self, Error> {
434    self._env(&StdEnv, Some(prefix))
435  }
436
437  /// Add a partial configuration from enviroment variables with a custom provider
438  ///
439  /// The provider must implement the [`EnvProvider`] trait
440  ///
441  /// The [`EnvProvider`] trait is already implemented for several kinds of Maps from the standard library
442  #[cfg(feature = "env")]
443  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
444  #[inline(always)]
445  #[allow(clippy::result_large_err)]
446  pub fn env_with_provider<E: EnvProvider>(&mut self, env: &E) -> Result<&mut Self, Error> {
447    self._env(env, None)
448  }
449
450  /// See [`Self::env_with_provider`] and [`Self::env_with_prefix`]
451  #[cfg(feature = "env")]
452  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
453  #[inline(always)]
454  #[allow(clippy::result_large_err)]
455  pub fn env_with_provider_and_prefix<E: EnvProvider>(
456    &mut self,
457    env: &E,
458    prefix: &str,
459  ) -> Result<&mut Self, Error> {
460    self._env(env, Some(prefix))
461  }
462
463  /// Add a partial configuration from in-memory code
464  #[inline(always)]
465  #[allow(clippy::result_large_err)]
466  pub fn code<S: AsRef<str>>(&mut self, code: S, format: Format) -> Result<&mut Self, Error> {
467    self._code(code.as_ref(), format, LoadLocation::Memory)
468  }
469
470  /// Add a partial configuration from in-memory code
471  ///
472  /// Specifying the [`LoadLocation`] of the in-memory code is useful for error reporting
473  #[inline(always)]
474  #[allow(clippy::result_large_err)]
475  pub fn code_with_location<S: AsRef<str>>(
476    &mut self,
477    code: S,
478    format: Format,
479    location: LoadLocation,
480  ) -> Result<&mut Self, Error> {
481    self._code(code.as_ref(), format, location)
482  }
483
484  /// Add a partial configuration from a url
485  #[cfg(feature = "url-blocking")]
486  #[cfg_attr(docsrs, doc(cfg(feature = "url-blocking")))]
487  #[allow(clippy::result_large_err)]
488  pub fn url(&mut self, url: &str, format: Format) -> Result<&mut Self, Error> {
489    let map_err = |e| Error::Network {
490      url: url.to_string(),
491      source: Arc::new(e),
492    };
493
494    let code = reqwest::blocking::get(url)
495      .map_err(map_err)?
496      .text()
497      .map_err(map_err)?;
498
499    self._code(&code, format, LoadLocation::Url(url.to_string()))
500  }
501
502  #[cfg(feature = "url-async")]
503  #[cfg_attr(docsrs, doc(cfg(feature = "url-async")))]
504  /// Add a partial configuration from a url, async version
505  pub async fn url_async(&mut self, url: &str, format: Format) -> Result<&mut Self, Error> {
506    let map_err = |e| Error::Network {
507      url: url.to_string(),
508      source: Arc::new(e),
509    };
510
511    let code = reqwest::get(url)
512      .await
513      .map_err(map_err)?
514      .text()
515      .await
516      .map_err(map_err)?;
517
518    self._code(&code, format, LoadLocation::Url(url.to_string()))
519  }
520
521  #[cfg(feature = "env")]
522  #[cfg_attr(docsrs, doc(cfg(feature = "env")))]
523  #[inline(always)]
524  #[allow(clippy::result_large_err)]
525  fn _env<E: EnvProvider>(&mut self, env: &E, prefix: Option<&str>) -> Result<&mut Self, Error> {
526    let partial = T::Partial::from_env_with_provider_and_optional_prefix(env, prefix)?;
527    self._add(partial)
528  }
529
530  #[allow(unused)]
531  #[allow(clippy::result_large_err)]
532  fn _code(
533    &mut self,
534    code: &str,
535    format: Format,
536    location: LoadLocation,
537  ) -> Result<&mut Self, Error> {
538    let partial = match format {
539      #[cfg(feature = "json")]
540      #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
541      Format::Json => serde_json::from_str(code).map_err(|e| Error::Json {
542        location,
543        source: Arc::new(e),
544      })?,
545
546      #[cfg(feature = "jsonc")]
547      #[cfg_attr(docsrs, doc(cfg(feature = "jsonc")))]
548      Format::Jsonc => {
549        let reader = json_comments::StripComments::new(code.as_bytes());
550        serde_json::from_reader(reader).map_err(|e| Error::Json {
551          location,
552          source: Arc::new(e),
553        })?
554      }
555
556      #[cfg(feature = "toml")]
557      #[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
558      Format::Toml => toml::from_str(code).map_err(|e| Error::Toml {
559        location,
560        source: e,
561      })?,
562
563      #[cfg(feature = "yaml")]
564      #[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
565      Format::Yaml => serde_yaml::from_str(code).map_err(|e| Error::Yaml {
566        location,
567        source: Arc::new(e),
568      })?,
569    };
570
571    self._add(partial)
572  }
573
574  /// Add a partial configuration from the `#[config(default = value)]` attributes
575  #[inline(always)]
576  #[allow(clippy::result_large_err)]
577  pub fn defaults(&mut self) -> Result<&mut Self, Error> {
578    self._add(T::Partial::defaults())
579  }
580
581  /// Add a pre generated partial configuration
582  #[inline(always)]
583  #[allow(clippy::result_large_err)]
584  pub fn partial(&mut self, partial: T::Partial) -> Result<&mut Self, Error> {
585    self._add(partial)
586  }
587
588  #[inline(always)]
589  #[allow(clippy::result_large_err)]
590  fn _add(&mut self, partial: T::Partial) -> Result<&mut Self, Error> {
591    self.partial.merge(partial)?;
592    Ok(self)
593  }
594
595  /// Get a reference to the partial configuration
596  #[inline(always)]
597  #[allow(clippy::result_large_err)]
598  pub fn partial_state(&self) -> &T::Partial {
599    &self.partial
600  }
601
602  /// Get a mutable reference to the partial configuration
603  #[inline(always)]
604  #[allow(clippy::result_large_err)]
605  pub fn partial_state_mut(&mut self) -> &mut T::Partial {
606    &mut self.partial
607  }
608
609  /// Get the final Config from the sum of all previously added stages
610  ///
611  /// this function will error if there are missing required properties
612  #[inline(always)]
613  #[allow(clippy::result_large_err)]
614  pub fn finish(self) -> Result<T, Error> {
615    let v = T::from_partial(self.partial)?;
616    Ok(v)
617  }
618}
619
620impl<T: Config> Default for ConfigLoader<T> {
621  fn default() -> Self {
622    Self::new()
623  }
624}