Add Builder type as convenient high level loading API

This commit is contained in:
Lukas Kalbertodt
2021-07-25 09:41:06 +02:00
parent 76373278b0
commit ca5bb2488f
4 changed files with 82 additions and 7 deletions

View File

@@ -41,13 +41,13 @@ fn main() -> Result<(), anyhow::Error> {
print!("{}", confique::toml::format::<Conf>(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(())
}

62
src/builder.rs Normal file
View File

@@ -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<C: Config> {
sources: Vec<Source<C>>,
}
impl<C: Config> Builder<C> {
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<PathBuf>) -> 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<C, Error> {
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<C: Config> {
File(PathBuf),
Preloaded(C::Partial),
}

View File

@@ -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<PathBuf>) -> Result<Self, Error> {

View File

@@ -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<Self, Error>;
/// 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<Self> {
Builder::new()
}
}
/// A potentially partial configuration object that can be directly deserialized