use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use config::{Config, Environment, File, FileFormat, Source};
use serde::{Deserialize, Serialize};
const APP_NAME: &str = env!("CARGO_PKG_NAME");
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default)]
pub(crate) struct ConfigSalusc {
socket_path: Option<String>,
agent_socket_path: Option<String>,
store_max_value_bytes: Option<usize>,
}
impl ConfigSalusc {
pub(crate) fn socket_path(&self) -> Option<&str> {
self.socket_path.as_deref()
}
pub(crate) fn agent_socket_path(&self) -> Option<&str> {
self.agent_socket_path.as_deref()
}
pub(crate) fn store_max_value_bytes(&self) -> Option<usize> {
self.store_max_value_bytes
}
}
pub(crate) fn load<S>(cli: &S, config_absolute_path: Option<&str>) -> Result<ConfigSalusc>
where
S: Source + Clone + Send + Sync + 'static,
{
let config_file_path = config_file_path(config_absolute_path)?;
let config = Config::builder()
.add_source(
File::from(config_file_path)
.format(FileFormat::Toml)
.required(false),
)
.add_source(env_source(&APP_NAME.to_ascii_uppercase()))
.add_source(cli.clone())
.build()
.context("unable to build salusc configuration")?;
config
.try_deserialize()
.context("unable to deserialize salusc configuration")
}
fn env_source(prefix: &str) -> Environment {
Environment::with_prefix(prefix)
.prefix_separator("_")
.separator("__")
.try_parsing(true)
}
fn config_file_path(config_absolute_path: Option<&str>) -> Result<PathBuf> {
config_absolute_path.map_or_else(
|| {
let base = dirs2::config_dir().context("there is no valid config directory")?;
Ok(config_file_in(&base, APP_NAME))
},
|path| Ok(PathBuf::from(path)),
)
}
fn config_file_in(base: &Path, app: &str) -> PathBuf {
base.join(app).join(app).with_extension("toml")
}
#[cfg(test)]
mod test {
use std::path::Path;
use anyhow::Result;
use config::{Config, Map};
use super::{ConfigSalusc, config_file_in, env_source};
#[test]
fn config_file_in_composes_app_dir_and_extension() {
let path = config_file_in(Path::new("/base"), "salusc");
assert_eq!(path, Path::new("/base/salusc/salusc.toml"));
}
#[test]
fn socket_path_from_env() -> Result<()> {
let mut env = Map::new();
let _old = env.insert(
"SALUSC_SOCKET_PATH".to_string(),
"/tmp/env.sock".to_string(),
);
let config = Config::builder()
.add_source(env_source("SALUSC").source(Some(env)))
.build()?;
let cfg: ConfigSalusc = config.try_deserialize()?;
assert_eq!(cfg.socket_path(), Some("/tmp/env.sock"));
Ok(())
}
#[test]
fn missing_socket_path_defaults_to_none() -> Result<()> {
let config = Config::builder().build()?;
let cfg: ConfigSalusc = config.try_deserialize()?;
assert!(cfg.socket_path().is_none());
Ok(())
}
}