use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct ConventionalPaths {
pub mob_toml: Option<PathBuf>,
pub gating_toml: Option<PathBuf>,
pub console_toml: Option<PathBuf>,
pub routing_toml: Option<PathBuf>,
pub contacts_toml: Option<PathBuf>,
pub schedule_files: Vec<PathBuf>,
}
impl ConventionalPaths {
pub fn discover(config_dir: impl AsRef<Path>, deployment_dir: impl AsRef<Path>) -> Self {
let config = config_dir.as_ref();
let deployment = deployment_dir.as_ref();
let mob_toml = check_file(config.join("mob.toml"));
let gating_toml = check_file(config.join("gating.toml"));
let console_toml = check_file(config.join("console.toml"));
let routing_toml = check_file(deployment.join("routing.toml"));
let contacts_toml = check_file(config.join("contacts.toml"));
let mut schedule_files = Vec::new();
if let Some(p) = check_file(config.join("defaults").join("schedules.toml")) {
schedule_files.push(p);
}
if let Some(p) = check_file(deployment.join("schedules.toml")) {
schedule_files.push(p);
}
Self {
mob_toml,
gating_toml,
console_toml,
routing_toml,
contacts_toml,
schedule_files,
}
}
pub fn schedule_file_strings(&self) -> Vec<String> {
self.schedule_files
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect()
}
}
fn check_file(path: PathBuf) -> Option<PathBuf> {
if path.is_file() { Some(path) } else { None }
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::fs;
#[test]
fn discover_finds_existing_files() {
let tmp = tempfile::tempdir().unwrap();
let config = tmp.path().join("config");
let deployment = tmp.path().join("deployment");
fs::create_dir_all(config.join("defaults")).unwrap();
fs::create_dir_all(&deployment).unwrap();
fs::write(config.join("mob.toml"), "[mob]\nid = \"test\"").unwrap();
fs::write(config.join("gating.toml"), "[[rules]]").unwrap();
fs::write(config.join("console.toml"), "[sidebar]").unwrap();
fs::write(
config.join("defaults").join("schedules.toml"),
"[[schedules]]",
)
.unwrap();
fs::write(deployment.join("routing.toml"), "[[routes]]").unwrap();
fs::write(deployment.join("schedules.toml"), "[[schedules]]").unwrap();
let paths = ConventionalPaths::discover(&config, &deployment);
assert!(paths.mob_toml.is_some());
assert!(paths.gating_toml.is_some());
assert!(paths.console_toml.is_some());
assert!(paths.routing_toml.is_some());
assert_eq!(paths.schedule_files.len(), 2);
}
#[test]
fn discover_handles_missing_files() {
let tmp = tempfile::tempdir().unwrap();
let config = tmp.path().join("config");
let deployment = tmp.path().join("deployment");
fs::create_dir_all(&config).unwrap();
fs::create_dir_all(&deployment).unwrap();
fs::write(config.join("mob.toml"), "[mob]\nid = \"test\"").unwrap();
let paths = ConventionalPaths::discover(&config, &deployment);
assert!(paths.mob_toml.is_some());
assert!(paths.gating_toml.is_none());
assert!(paths.console_toml.is_none());
assert!(paths.routing_toml.is_none());
assert!(paths.schedule_files.is_empty());
}
#[test]
fn discover_handles_nonexistent_dirs() {
let paths = ConventionalPaths::discover("/nonexistent/config", "/nonexistent/deployment");
assert!(paths.mob_toml.is_none());
assert!(paths.gating_toml.is_none());
assert!(paths.console_toml.is_none());
assert!(paths.routing_toml.is_none());
assert!(paths.schedule_files.is_empty());
}
#[test]
fn schedule_files_ordered_defaults_first() {
let tmp = tempfile::tempdir().unwrap();
let config = tmp.path().join("config");
let deployment = tmp.path().join("deployment");
fs::create_dir_all(config.join("defaults")).unwrap();
fs::create_dir_all(&deployment).unwrap();
fs::write(config.join("defaults").join("schedules.toml"), "default").unwrap();
fs::write(deployment.join("schedules.toml"), "override").unwrap();
let paths = ConventionalPaths::discover(&config, &deployment);
assert_eq!(paths.schedule_files.len(), 2);
assert!(
paths.schedule_files[0]
.to_string_lossy()
.contains("defaults")
);
assert!(
paths.schedule_files[1]
.to_string_lossy()
.contains("deployment")
);
}
}