use crate::{
cli::Cli,
config::{Config, ConfigFormat},
environment::Environment,
error::{Error, Result},
merge::{ConfigMerger, MergeStrategy},
source::ConfigSource,
};
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::path::Path;
type ValidationFn = Box<dyn Fn(&Value) -> Result<()>>;
pub struct ConfigBuilder {
sources: Vec<Box<dyn ConfigSource>>,
merge_strategy: MergeStrategy,
validate: Option<ValidationFn>,
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl ConfigBuilder {
pub fn new() -> Self {
Self {
sources: Vec::new(),
merge_strategy: MergeStrategy::Deep,
validate: None,
}
}
pub fn with_merge_strategy(mut self, strategy: MergeStrategy) -> Self {
self.merge_strategy = strategy;
self
}
pub fn add_source(mut self, source: Box<dyn ConfigSource>) -> Self {
self.sources.push(source);
self
}
pub fn with_env(self, prefix: impl Into<String>) -> Self {
let env_source = Environment::new().with_prefix(prefix);
self.add_source(Box::new(env_source))
}
pub fn with_env_custom(self, env: Environment) -> Self {
self.add_source(Box::new(env))
}
pub fn with_file(self, path: impl AsRef<Path>) -> Result<Self> {
let config = Config::from_file(path)?;
Ok(self.add_source(Box::new(config)))
}
pub fn with_file_optional(self, path: impl AsRef<Path>) -> Result<Self> {
let config = Config::from_file_optional(path)?;
Ok(self.add_source(Box::new(config)))
}
pub fn with_file_format(self, path: impl AsRef<Path>, format: ConfigFormat) -> Result<Self> {
let config = Config::with_format(path, format)?;
Ok(self.add_source(Box::new(config)))
}
pub fn with_cli(self) -> Self {
let cli = Cli::from_args();
self.add_source(Box::new(cli))
}
pub fn with_cli_custom(self, cli: Cli) -> Self {
self.add_source(Box::new(cli))
}
pub fn with_clap<T: clap::Parser + serde::Serialize>(self) -> Result<Self> {
let cli = Cli::with_clap_app::<T>()?;
Ok(self.add_source(Box::new(cli)))
}
pub fn with_defaults(mut self, defaults: Value) -> Result<Self> {
struct DefaultsSource {
value: Value,
}
impl ConfigSource for DefaultsSource {
fn collect(&self) -> Result<Value> {
Ok(self.value.clone())
}
fn source_type(&self) -> crate::source::Source {
crate::source::Source::Default
}
fn has_value(&self, key: &str) -> bool {
self.value.get(key).is_some()
}
fn get_value(&self, key: &str) -> Option<Value> {
self.value.get(key).cloned()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
self.sources
.insert(0, Box::new(DefaultsSource { value: defaults }));
Ok(self)
}
pub fn validate_with<F>(mut self, validator: F) -> Self
where
F: Fn(&Value) -> Result<()> + 'static,
{
self.validate = Some(Box::new(validator));
self
}
pub fn build<T: DeserializeOwned>(self) -> Result<T> {
let merger = ConfigMerger::new(self.merge_strategy);
let mut source_values = Vec::new();
for source in &self.sources {
let value = source.collect()?;
let priority = source.source_type().priority();
source_values.push((value, priority));
}
let merged = merger.merge_sources(source_values);
if let Some(validator) = &self.validate {
validator(&merged)?;
}
serde_json::from_value(merged)
.map_err(|e| Error::Serialization(format!("Failed to deserialize config: {e}")))
}
pub fn build_value(self) -> Result<Value> {
let merger = ConfigMerger::new(self.merge_strategy);
let mut source_values = Vec::new();
for source in &self.sources {
let value = source.collect()?;
let priority = source.source_type().priority();
source_values.push((value, priority));
}
let merged = merger.merge_sources(source_values);
if let Some(validator) = &self.validate {
validator(&merged)?;
}
Ok(merged)
}
pub fn sources(&self) -> &[Box<dyn ConfigSource>] {
&self.sources
}
pub fn get_source<T: ConfigSource + 'static>(&self) -> Option<&T> {
self.sources
.iter()
.find_map(|source| source.as_any().downcast_ref::<T>())
}
}