1use crate::error::ConfigulatorError;
2use crate::options::FileOptions;
3use crate::value_map::{ConfigValue, ValueMap};
4
5pub trait FileLoader: Send + Sync {
13 fn load(&self, contents: &str) -> Result<ValueMap, ConfigulatorError>;
15}
16
17pub struct SerdeLoader<F>(F);
22
23impl<F, E> FileLoader for SerdeLoader<F>
24where
25 F: Fn(&str) -> Result<ConfigValue, E> + Send + Sync,
26 E: std::fmt::Display,
27{
28 fn load(&self, contents: &str) -> Result<ValueMap, ConfigulatorError> {
29 let value = (self.0)(contents)
30 .map_err(|e| ConfigulatorError::FileError(e.to_string()))?;
31 match value {
32 ConfigValue::Nested(map) => Ok(map),
33 ConfigValue::Scalar(_) => Err(ConfigulatorError::FileError(
34 "config file root must be a mapping/table, got a scalar value".into(),
35 )),
36 ConfigValue::List(_) => Err(ConfigulatorError::FileError(
37 "config file root must be a mapping/table, got a list".into(),
38 )),
39 }
40 }
41}
42
43pub fn serde_loader<F, E>(f: F) -> Box<dyn FileLoader>
61where
62 F: Fn(&str) -> Result<ConfigValue, E> + Send + Sync + 'static,
63 E: std::fmt::Display + 'static,
64{
65 Box::new(SerdeLoader(f))
66}
67
68pub fn load_from_file(opts: &FileOptions) -> Result<ValueMap, ConfigulatorError> {
71 let mut contents = None;
72 for path in &opts.paths {
73 match std::fs::read_to_string(path) {
74 Ok(data) => {
75 contents = Some(data);
76 break;
77 }
78 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
79 Err(e) => {
80 return Err(ConfigulatorError::FileError(format!(
81 "{}: {e}",
82 path.display()
83 )));
84 }
85 }
86 }
87
88 let contents = match contents {
89 Some(c) => c,
90 None => {
91 if opts.error_if_not_found {
92 return Err(ConfigulatorError::FileNotFound);
93 }
94 return Ok(ValueMap::new());
95 }
96 };
97
98 opts.loader.load(&contents)
99}