use std::collections::HashMap;
use std::env::VarError;
use std::sync::Arc;
use async_trait::async_trait;
use super::ConfigError;
#[async_trait]
pub trait ConfigSource: Send + Sync {
async fn load(&self) -> Result<(), ConfigError>;
async fn get(&self, key: &str) -> Result<Option<String>, ConfigError>;
async fn get_or_default(&self, key: &str, default: String) -> Result<String, ConfigError> {
Ok(self.get(key).await?.unwrap_or(default))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvSource;
#[async_trait]
impl ConfigSource for EnvSource {
async fn load(&self) -> Result<(), ConfigError> {
Ok(())
}
async fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
match std::env::var(key) {
Ok(value) => Ok(Some(value)),
Err(VarError::NotPresent) => Ok(None),
Err(VarError::NotUnicode(_)) => Err(ConfigError::Source(format!(
"environment variable {key} is not valid unicode"
))),
}
}
}
#[derive(Debug, Clone)]
pub struct MapSource {
values: HashMap<String, String>,
}
impl MapSource {
pub fn new(values: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>) -> Self {
Self {
values: values
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
}
}
}
#[async_trait]
impl ConfigSource for MapSource {
async fn load(&self) -> Result<(), ConfigError> {
Ok(())
}
async fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
Ok(self.values.get(key).cloned())
}
}
#[async_trait]
impl<T: ConfigSource + ?Sized> ConfigSource for Arc<T> {
async fn load(&self) -> Result<(), ConfigError> {
(**self).load().await
}
async fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
(**self).get(key).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::executor::block_on;
#[test]
fn map_source_get_and_default() {
let src = MapSource::new([("K", "v")]);
block_on(async {
assert_eq!(src.get("K").await.unwrap().as_deref(), Some("v"));
assert_eq!(src.get("missing").await.unwrap(), None);
assert_eq!(
src.get_or_default("missing", "d".into()).await.unwrap(),
"d"
);
});
}
#[test]
fn env_source_absent_is_none() {
let src = EnvSource;
block_on(async {
assert_eq!(
src.get("NOEMA_TEST_CONFIG_ABSENT_KEY_XYZ").await.unwrap(),
None
);
});
}
}