mod env;
pub mod error;
mod file;
mod proxy;
#[cfg(feature = "vault-config")]
mod vault;
#[cfg(test)]
#[path = "../../tests/unit/config/tests.rs"]
mod tests;
pub use env::EnvConfigProvider;
pub use error::ConfigError;
pub use file::FileConfigProvider;
#[cfg(feature = "vault-config")]
pub use vault::VaultConfigProvider;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::fmt::Debug;
use std::sync::Arc;
pub trait ConfigProvider: Debug + Send + Sync {
fn has(&self, key: &str) -> bool;
fn provider_name(&self) -> &str;
fn get_raw(&self, key: &str) -> Result<Option<Value>, ConfigError>;
}
pub trait ConfigProviderExt: ConfigProvider {
fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, ConfigError> {
match self.get_raw(key)? {
Some(value) => serde_json::from_value(value).map(Some).map_err(|e| {
ConfigError::ParseError(format!("failed to deserialize '{key}': {e}"))
}),
None => Ok(None),
}
}
}
impl<T: ConfigProvider> ConfigProviderExt for T {}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
providers: Vec<Arc<dyn ConfigProvider>>,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_provider<P: ConfigProvider + 'static>(mut self, provider: P) -> Self {
self.providers.push(Arc::new(provider));
self
}
pub fn build(self) -> Config {
Config {
providers: self.providers,
}
}
}
#[derive(Debug, Clone)]
pub struct Config {
providers: Vec<Arc<dyn ConfigProvider>>,
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
fn get_raw(&self, key: &str) -> Result<Option<Value>, ConfigError> {
for provider in self.providers.iter().rev() {
if provider.has(key) {
return provider.get_raw(key);
}
}
Ok(None)
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, ConfigError> {
match self.get_raw(key)? {
Some(value) => serde_json::from_value(value).map(Some).map_err(|e| {
ConfigError::ParseError(format!("failed to deserialize '{key}': {e}"))
}),
None => Ok(None),
}
}
pub fn get_or_default<T: DeserializeOwned>(
&self,
key: &str,
default: T,
) -> Result<T, ConfigError> {
match self.get(key)? {
Some(value) => Ok(value),
None => Ok(default),
}
}
pub fn default_file(file_path: &str) -> Result<Self, ConfigError> {
let provider = FileConfigProvider::new(file_path)?;
Ok(Self::builder().with_provider(provider).build())
}
}