diff options
author | Radosław Kot <rdkt13@gmail.com> | 2021-04-24 18:52:51 +0200 |
---|---|---|
committer | Radosław Kot <rdkt13@gmail.com> | 2021-05-08 17:43:16 +0200 |
commit | 82d23c76a8637360e03c0b43d9a1c0c26d820d9f (patch) | |
tree | a1b823412e7c3e334727c02e8991209709a861c4 | |
parent | 79883ff7f8ab246c8993f6276d97e5670271cb8c (diff) |
Add ConfigBuilder
-rw-r--r-- | src/builder.rs | 164 | ||||
-rw-r--r-- | src/config.rs | 19 | ||||
-rw-r--r-- | src/lib.rs | 2 |
3 files changed, 185 insertions, 0 deletions
diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..8f3081c --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,164 @@ +use std::str::FromStr; +use std::{collections::HashMap, iter::IntoIterator}; + +use crate::{ + config::Config, error, error::ConfigError, path::Expression, source::Source, value::Value, +}; + +/// A configuration builder +/// +/// It registers ordered sources of configuration to later build consistent [`Config`] from them. +/// Configuration sources it defines are defaults, [`Source`]s and overrides. +/// +/// Defaults are alaways loaded first and can be overwritten by any of two other sources. +/// Overrides are always loaded last, thus cannot be overridden. +/// Both can be only set explicitly key by key in code +/// using [`set_default`](Self::set_default) or [`set_override`](Self::set_override). +/// +/// An intermediate category, [`Source`], set groups of keys at once implicitly using data coming from external sources +/// like files, environment variables or others that one implements. Defining a [`Source`] is as simple as implementing +/// a trait for a struct. +/// +/// Adding sources, setting defaults and overrides does not invoke any I/O nor builds a config. +/// It happens on demand when [`build`](Self::build) (or its alternative) is called. +/// Therefore all errors, related to any of the [`Source`] will only show up then. +/// +/// # Examples +/// +/// ```rust +/// # use config::*; +/// # use std::error::Error; +/// # fn main() -> Result<(), Box<dyn Error>> { +/// let mut builder = ConfigBuilder::default(); +/// +/// builder.set_default("default", "1")?; +/// builder.add_source(File::new("config/settings", FileFormat::Json)); +/// builder.set_override("override", "1")?; +/// +/// match builder.build() { +/// Ok(config) => { +/// // use your config +/// }, +/// Err(e) => { +/// // something went wrong +/// } +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// Calls can be chained as well +/// ```rust +/// # use std::error::Error; +/// # use config::*; +/// # fn main() -> Result<(), Box<dyn Error>> { +/// let mut builder = ConfigBuilder::default(); +/// +/// builder +/// .set_default("default", "1")? +/// .add_source(File::new("config/settings", FileFormat::Json)) +/// .add_source(File::new("config/settings.prod", FileFormat::Json)) +/// .set_override("override", "1")?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone, Default)] +pub struct ConfigBuilder { + defaults: HashMap<Expression, Value>, + overrides: HashMap<Expression, Value>, + sources: Vec<Box<dyn Source + Send + Sync>>, +} + +impl ConfigBuilder { + /// Set a default `value` at `key` + /// + /// This value can be overwritten by any [`Source`] or override. + /// + /// # Errors + /// + /// Fails if `Expression::from_str(key)` fails. + pub fn set_default<S, T>(&mut self, key: S, value: T) -> error::Result<&mut ConfigBuilder> + where + S: AsRef<str>, + T: Into<Value>, + { + self.defaults + .insert(Expression::from_str(key.as_ref())?, value.into()); + Ok(self) + } + + /// Registers new [`Source`] in this builder. + /// + /// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use. + pub fn add_source<T>(&mut self, source: T) -> &mut Self + where + T: Source + Send + Sync + 'static, + { + self.sources.push(Box::new(source)); + self + } + + /// Set an override + /// + /// This function sets an overwrite value. It will not be altered by any default or [`Source`] + /// + /// # Errors + /// + /// Fails if `Expression::from_str(key)` fails. + pub fn set_override<S, T>(&mut self, key: S, value: T) -> error::Result<&mut ConfigBuilder> + where + S: AsRef<str>, + T: Into<Value>, + { + self.overrides + .insert(Expression::from_str(key.as_ref())?, value.into()); + Ok(self) + } + + /// Reads all registered [`Source`]s. + /// + /// This is the method that invokes all I/O operations. + /// For a non consuming alternative see [`build_cloned`](Self::build_cloned) + /// + /// # Errors + /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, + /// this method returns error. + pub fn build(self) -> error::Result<Config> { + Self::build_internal(self.defaults, self.overrides, &self.sources) + } + + /// Reads all registered [`Source`]s. + /// + /// Similar to [`build`](Self::build), but it does not take ownership of `ConfigBuilder` to allow later reuse. + /// Internally it clones data to achieve it. + /// + /// # Errors + /// If source collection fails, be it technical reasons or related to inability to read data as `Config` for different reasons, + /// this method returns error. + pub fn build_cloned(&self) -> error::Result<Config> { + Self::build_internal(self.defaults.clone(), self.overrides.clone(), &self.sources) + } + + fn build_internal( + defaults: HashMap<Expression, Value>, + overrides: HashMap<Expression, Value>, + sources: &Vec<Box<dyn Source + Send + Sync>>, + ) -> error::Result<Config> { + let mut cache: Value = HashMap::<String, Value>::new().into(); + + // Add defaults + for (key, val) in defaults.into_iter() { + key.set(&mut cache, val); + } + + // Add sources + sources.collect_to(&mut cache)?; + + // Add overrides + for (key, val) in overrides.into_iter() { + key.set(&mut cache, val); + } + + Ok(Config::new(cache)) + } +} diff --git a/src/config.rs b/src/config.rs index 2b6c5b6..5b4d40e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::fmt::Debug; +use builder::ConfigBuilder; use serde::de::Deserialize; use serde::ser::Serialize; @@ -35,7 +36,20 @@ impl Default for Config { } impl Config { + pub(crate) fn new(value: Value) -> Self { + Config { + cache: value, + ..Default::default() + } + } + + /// Creates new [`ConfigBuilder`] instance + pub fn builder() -> ConfigBuilder { + ConfigBuilder::default() + } + /// Merge in a configuration property source. + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn merge<T>(&mut self, source: T) -> Result<&mut Config> where T: 'static, @@ -46,6 +60,7 @@ impl Config { } /// Merge in a configuration property source. + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn with_merged<T>(mut self, source: T) -> Result<Self> where T: 'static, @@ -61,6 +76,7 @@ impl Config { /// /// Configuration is automatically refreshed after a mutation /// operation (`set`, `merge`, `set_default`, etc.). + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn refresh(&mut self) -> Result<&mut Config> { self.cache = { let mut cache: Value = HashMap::<String, Value>::new().into(); @@ -85,6 +101,7 @@ impl Config { } /// Set a default `value` at `key` + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<&mut Config> where T: Into<Value>, @@ -101,6 +118,7 @@ impl Config { /// # Warning /// /// Errors if config is frozen + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn set<T>(&mut self, key: &str, value: T) -> Result<&mut Config> where T: Into<Value>, @@ -109,6 +127,7 @@ impl Config { self.refresh() } + #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")] pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> { let expr: path::Expression = key.parse()?; @@ -51,6 +51,7 @@ extern crate ini; #[cfg(feature = "ron")] extern crate ron; +mod builder; mod config; mod de; mod env; @@ -61,6 +62,7 @@ mod ser; mod source; mod value; +pub use crate::builder::ConfigBuilder; pub use crate::config::Config; pub use crate::env::Environment; pub use crate::error::ConfigError; |