use crate::config::error::Error;
use crate::config::source::{DotEnvSource, EnvSource, JsonSource, Source, YamlSource};
use std::path::Path;
use std::sync::Arc;
#[derive(Default)]
pub struct Builder {
sources: Vec<Arc<dyn Source>>,
}
impl Builder {
pub fn source(mut self, source: impl Source + 'static) -> Self {
self.sources.push(Arc::new(source));
self
}
pub fn json(self, path: impl AsRef<Path>, priority: u32) -> Self {
self.source(JsonSource::new(path, priority))
}
pub fn yaml(self, path: impl AsRef<Path>, priority: u32) -> Self {
self.source(YamlSource::new(path, priority))
}
pub fn toml(self, path: impl AsRef<Path>, priority: u32) -> Self {
self.source(crate::config::source::TomlSource::new(path, priority))
}
pub fn env(
self,
prefix: impl Into<String>,
separator: impl Into<String>,
priority: u32,
) -> Self {
self.source(EnvSource::new(prefix, separator, priority))
}
pub fn dotenv(
self,
path: impl AsRef<Path>,
prefix: impl Into<String>,
separator: impl Into<String>,
priority: u32,
) -> Result<Self, Error> {
let source = DotEnvSource::new(path, &prefix.into(), &separator.into(), priority)?;
Ok(self.source(source))
}
#[cfg(feature = "azure")]
pub fn azure(
self,
endpoint: impl Into<String>,
credential: std::sync::Arc<dyn azure_core::credentials::TokenCredential>,
priority: u32,
) -> Self {
let source =
crate::config::source::AzureAppConfigSource::new(endpoint, credential, priority);
self.source(source)
}
pub fn env_keys<I, S>(self, keys: I, separator: impl Into<String>, priority: u32) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.source(EnvSource::with_keys(keys, separator, priority))
}
pub fn build_sources(mut self) -> Vec<Arc<dyn Source>> {
self.sources.sort_by_key(|s| s.priority());
self.sources
}
pub async fn build(self) -> Result<crate::config::store::Store, Error> {
crate::config::store::Store::from_builder(self).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn builder_stacks_sources() {
let builder = Builder::default().json("/tmp/base.json", 100);
let sources = builder.build_sources();
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].name(), "json");
assert_eq!(sources[0].priority(), 100);
}
#[tokio::test]
async fn toml_source_is_registered() {
let builder = Builder::default().toml("/tmp/config.toml", 50);
let sources = builder.build_sources();
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].name(), "toml");
assert_eq!(sources[0].priority(), 50);
}
#[tokio::test]
async fn multiple_sources_sorted_by_priority() {
let builder = Builder::default()
.json("/tmp/base.json", 100)
.yaml("/tmp/override.yaml", 50)
.env("APP_", "__", 10);
let sources = builder.build_sources();
assert_eq!(sources.len(), 3);
assert_eq!(sources[0].priority(), 10);
assert_eq!(sources[0].name(), "env");
assert_eq!(sources[1].priority(), 50);
assert_eq!(sources[1].name(), "yaml");
assert_eq!(sources[2].priority(), 100);
assert_eq!(sources[2].name(), "json");
}
#[tokio::test]
async fn source_method_adds_custom_source() {
use crate::config::source::JsonSource;
let builder = Builder::default().source(JsonSource::new("/tmp/custom.json", 25));
let sources = builder.build_sources();
assert_eq!(sources.len(), 1);
assert_eq!(sources[0].priority(), 25);
}
#[tokio::test]
async fn dotenv_method_works() {
use crate::config::source::test_helpers::{write_temp, EnvGuard};
let f = write_temp("CONFIGKIT_BUILDER_KEY=built\n");
let builder = Builder::default().dotenv(f.path(), "CONFIGKIT_B", "__", 5);
let _guard = EnvGuard::remove_on_drop("CONFIGKIT_BUILDER_KEY");
assert!(builder.is_ok());
let sources = builder.unwrap().build_sources();
assert_eq!(sources[0].priority(), 5);
assert_eq!(sources[0].name(), "dotenv");
}
#[tokio::test]
async fn default_creates_empty_builder() {
let builder = Builder::default();
let sources = builder.build_sources();
assert!(sources.is_empty());
}
}