use crate::error::ConfigulatorError;
use crate::options::FileOptions;
use crate::value_map::{ConfigValue, ValueMap};
pub trait FileLoader: Send + Sync {
fn load(&self, contents: &str) -> Result<ValueMap, ConfigulatorError>;
}
pub struct SerdeLoader<F>(F);
impl<F, E> FileLoader for SerdeLoader<F>
where
F: Fn(&str) -> Result<ConfigValue, E> + Send + Sync,
E: std::fmt::Display,
{
fn load(&self, contents: &str) -> Result<ValueMap, ConfigulatorError> {
let value = (self.0)(contents)
.map_err(|e| ConfigulatorError::FileError(e.to_string()))?;
match value {
ConfigValue::Nested(map) => Ok(map),
ConfigValue::Scalar(_) => Err(ConfigulatorError::FileError(
"config file root must be a mapping/table, got a scalar value".into(),
)),
ConfigValue::List(_) => Err(ConfigulatorError::FileError(
"config file root must be a mapping/table, got a list".into(),
)),
}
}
}
pub fn serde_loader<F, E>(f: F) -> Box<dyn FileLoader>
where
F: Fn(&str) -> Result<ConfigValue, E> + Send + Sync + 'static,
E: std::fmt::Display + 'static,
{
Box::new(SerdeLoader(f))
}
pub fn load_from_file(opts: &FileOptions) -> Result<ValueMap, ConfigulatorError> {
let mut contents = None;
for path in &opts.paths {
match std::fs::read_to_string(path) {
Ok(data) => {
contents = Some(data);
break;
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(e) => {
return Err(ConfigulatorError::FileError(format!(
"{}: {e}",
path.display()
)));
}
}
}
let contents = match contents {
Some(c) => c,
None => {
if opts.error_if_not_found {
return Err(ConfigulatorError::FileNotFound);
}
return Ok(ValueMap::new());
}
};
opts.loader.load(&contents)
}