From ca5bb2488fbee60044087c25556f99f3fc87b08c Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sun, 25 Jul 2021 09:41:06 +0200 Subject: [PATCH] Add `Builder` type as convenient high level loading API --- examples/simple.rs | 12 ++++----- src/builder.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++ src/file.rs | 2 +- src/lib.rs | 13 ++++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 src/builder.rs diff --git a/examples/simple.rs b/examples/simple.rs index 1abea80..d84dc03 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -41,13 +41,13 @@ fn main() -> Result<(), anyhow::Error> { print!("{}", confique::toml::format::(FormatOptions::default())); println!("--------------------------------------------------------"); - // let r = Conf::from_sources(&[ - // &Path::new("examples/files/simple.toml"), - // &Path::new("examples/files/etc/simple.yaml"), - // ])?; + let r = Conf::builder() + .file("examples/files/simple.toml") + .file("examples/files/etc/simple.yaml") + .load()?; - // println!(); - // println!("LOADED CONFIGURATION: {:#?}", r); + println!(); + println!("LOADED CONFIGURATION: {:#?}", r); Ok(()) } diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..53e2b47 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,62 @@ +use std::path::PathBuf; + +use crate::{Config, Error, File, Partial}; + + +/// Convenience builder to configure, load and merge multiple configuration +/// sources. +/// +/// **Sources specified earlier have a higher priority**. Obtained via +/// [`Config::builder`]. +pub struct Builder { + sources: Vec>, +} + +impl Builder { + pub(crate) fn new() -> Self { + Self { + sources: vec![], + } + } + + /// Adds a configuration file as source. Infers the format from the file + /// extension. If the path has no file extension or the extension is + /// unknown, [`Builder::load`] will return an error. + /// + /// The file is not considered required: if the file does not exist, an + /// empty configuration (`C::Partial::empty()`) is used for this layer. + pub fn file(mut self, path: impl Into) -> Self { + self.sources.push(Source::File(path.into())); + self + } + + /// Adds an already loaded partial configuration as source. + pub fn preloaded(mut self, partial: C::Partial) -> Self { + self.sources.push(Source::Preloaded(partial)); + self + } + + /// Loads all configured sources in order. Earlier sources have a higher + /// priority, later sources only fill potential gaps. + /// + /// Will return an error if loading the sources fails or if the merged + /// configuration does not specify all required values. + pub fn load(self) -> Result { + let mut partial = C::Partial::empty(); + for source in self.sources { + let layer = match source { + Source::File(path) => File::new(path)?.load()?, + Source::Preloaded(p) => p, + }; + + partial = partial.with_fallback(layer); + } + + C::from_partial(partial.with_fallback(C::Partial::default_values())) + } +} + +enum Source { + File(PathBuf), + Preloaded(C::Partial), +} diff --git a/src/file.rs b/src/file.rs index 6a74ecd..d61ee48 100644 --- a/src/file.rs +++ b/src/file.rs @@ -14,7 +14,7 @@ pub struct File { } impl File { - /// Configuration file with the given path. The format is inferred by the + /// Configuration file with the given path. The format is inferred from the /// file extension. If the path does not have an extension or it is /// unknown, an error is returned. pub fn new(path: impl Into) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 9b4f63a..314f1f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use serde::Deserialize; #[doc(hidden)] pub mod internal; +mod builder; mod error; mod file; pub mod meta; @@ -14,6 +15,7 @@ pub mod toml; pub use serde; pub use confique_macro::Config; pub use self::{ + builder::Builder, error::Error, file::{File, FileFormat}, }; @@ -55,6 +57,17 @@ pub trait Config: Sized { /// If any required values are not defined in `partial`, an [`Error`] is /// returned. fn from_partial(partial: Self::Partial) -> Result; + + /// Convenience builder to configure, load and merge multiple configuration + /// sources. **Sources specified earlier have a higher priority**; later + /// sources only fill in the gaps. After all sources have been loaded, the + /// default values (usually specified with `#[default = ...]`) are merged + /// (with the lowest priority). + /// + /// TODO: Example + fn builder() -> Builder { + Builder::new() + } } /// A potentially partial configuration object that can be directly deserialized