summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRadosław Kot <rdkt13@gmail.com>2021-04-24 18:52:51 +0200
committerRadosław Kot <rdkt13@gmail.com>2021-05-08 17:43:16 +0200
commit82d23c76a8637360e03c0b43d9a1c0c26d820d9f (patch)
treea1b823412e7c3e334727c02e8991209709a861c4
parent79883ff7f8ab246c8993f6276d97e5670271cb8c (diff)
Add ConfigBuilder
-rw-r--r--src/builder.rs164
-rw-r--r--src/config.rs19
-rw-r--r--src/lib.rs2
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()?;
diff --git a/src/lib.rs b/src/lib.rs
index 2dbd7b8..ae02c36 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;