use std::path::{Path, PathBuf};
const SETTINGS_FILE_EXTENSIONS: &[&str] =
&["toml", "json", "yaml", "ini", "hjson"];
const SETTINGS_DIRS: &[&str] = &["", "config"];
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FileSources {
pub settings: Option<PathBuf>,
pub secrets: Option<PathBuf>,
pub dotenv: Vec<PathBuf>,
}
impl FileSources {
pub fn from_root(root_path: PathBuf, env: &str) -> Self {
let mut sources = Self {
settings: None,
secrets: None,
dotenv: Vec::new(),
};
let mut settings_found = false;
let candidates = walk_to_root(root_path);
for cand in candidates {
let dotenv_cand = cand.join(".env");
if dotenv_cand.exists() {
sources.dotenv.push(dotenv_cand);
}
let dotenv_cand = cand.join(format!(".env.{}", env));
if dotenv_cand.exists() {
sources.dotenv.push(dotenv_cand);
}
'outer: for &settings_dir in SETTINGS_DIRS {
let dir = cand.join(settings_dir);
for &ext in SETTINGS_FILE_EXTENSIONS {
let settings_cand = dir.join(format!("settings.{}", ext));
if settings_cand.exists() {
sources.settings = Some(settings_cand);
settings_found = true;
}
let secrets_cand = dir.join(format!(".secrets.{}", ext));
if secrets_cand.exists() {
sources.secrets = Some(secrets_cand);
settings_found = true;
}
if settings_found {
break 'outer;
}
}
}
if sources.any() {
break;
}
}
sources
}
fn any(&self) -> bool {
self.settings.is_some()
|| self.secrets.is_some()
|| !self.dotenv.is_empty()
}
}
pub fn walk_to_root(mut path: PathBuf) -> Vec<PathBuf> {
let mut candidates = Vec::new();
if path.is_file() {
path = path.parent().unwrap_or_else(|| Path::new("/")).into();
}
for ancestor in path.ancestors() {
candidates.push(ancestor.to_path_buf());
}
candidates
}
#[cfg(test)]
mod test {
use super::*;
use std::env;
fn get_data_path(suffix: &str) -> PathBuf {
let mut target_dir = PathBuf::from(
env::current_exe()
.expect("exe path")
.parent()
.expect("exe parent"),
);
while target_dir.file_name() != Some(std::ffi::OsStr::new("target")) {
if !target_dir.pop() {
panic!("Cannot find target directory");
}
}
target_dir.pop();
target_dir.join(format!("tests/data{}", suffix))
}
#[test]
fn test_walk_to_root_dir() {
assert_eq!(
walk_to_root(PathBuf::from("/a/dir/located/somewhere")),
vec![
PathBuf::from("/a/dir/located/somewhere"),
PathBuf::from("/a/dir/located"),
PathBuf::from("/a/dir"),
PathBuf::from("/a"),
PathBuf::from("/"),
],
);
}
#[test]
fn test_walk_to_root_root() {
assert_eq!(walk_to_root(PathBuf::from("/")), vec![PathBuf::from("/")],);
}
#[test]
fn test_sources() {
let data_path = get_data_path("");
assert_eq!(
FileSources::from_root(data_path.clone(), "development"),
FileSources {
settings: Some(data_path.clone().join("config/settings.toml")),
secrets: Some(data_path.join("config/.secrets.toml")),
dotenv: vec![data_path.join(".env")],
},
);
let data_path = get_data_path("2");
assert_eq!(
FileSources::from_root(data_path.clone(), "development"),
FileSources {
settings: Some(data_path.clone().join("config/settings.toml")),
secrets: Some(data_path.join("config/.secrets.toml")),
dotenv: vec![
data_path.join(".env"),
data_path.join(".env.development")
],
},
);
let data_path = get_data_path("2");
assert_eq!(
FileSources::from_root(data_path.clone(), "production"),
FileSources {
settings: Some(data_path.clone().join("config/settings.toml")),
secrets: Some(data_path.join("config/.secrets.toml")),
dotenv: vec![data_path.join(".env")],
},
);
let data_path = get_data_path("3");
assert_eq!(
FileSources::from_root(data_path.clone(), "development"),
FileSources {
settings: Some(data_path.clone().join("settings.toml")),
secrets: Some(data_path.join(".secrets.toml")),
dotenv: vec![data_path.join(".env")],
},
);
let data_path = get_data_path("3");
assert_eq!(
FileSources::from_root(data_path.clone(), "production"),
FileSources {
settings: Some(data_path.clone().join("settings.toml")),
secrets: Some(data_path.join(".secrets.toml")),
dotenv: vec![
data_path.join(".env"),
data_path.join(".env.production")
],
},
);
}
}