Skip to main content

prns_config/
discovery.rs

1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use prns_core::persistence::reticulum_directory;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct DiscoveredConfig {
8    pub dir: PathBuf,
9    pub config: Option<PathBuf>,
10}
11
12impl DiscoveredConfig {
13    pub fn is_empty(&self) -> bool {
14        self.config.is_none()
15    }
16}
17
18#[derive(Debug)]
19pub enum DiscoveryError {
20    HomeDirectoryUnavailable,
21}
22
23impl fmt::Display for DiscoveryError {
24    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::HomeDirectoryUnavailable => formatter.write_str(
27                "could not determine the Reticulum config directory; pass --config explicitly",
28            ),
29        }
30    }
31}
32
33impl std::error::Error for DiscoveryError {}
34
35fn probe(dir: PathBuf, exists: &impl Fn(&Path) -> bool) -> DiscoveredConfig {
36    let config = dir.join(reticulum_directory::CONFIG_FILE_NAME);
37    DiscoveredConfig {
38        config: exists(&config).then_some(config),
39        dir,
40    }
41}
42
43pub fn discover(override_dir: Option<&Path>) -> Result<DiscoveredConfig, DiscoveryError> {
44    let dir = match override_dir {
45        Some(dir) => dir.to_path_buf(),
46        None => reticulum_directory::resolve().ok_or(DiscoveryError::HomeDirectoryUnavailable)?,
47    };
48    Ok(probe(dir, &|path: &Path| path.is_file()))
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use std::collections::HashSet;
55
56    fn world(files: &[&str]) -> impl Fn(&Path) -> bool {
57        let present: HashSet<PathBuf> = files.iter().map(PathBuf::from).collect();
58        move |path: &Path| present.contains(path)
59    }
60
61    #[test]
62    fn an_override_wins_outright_even_when_empty() {
63        let dir = tempfile::tempdir().unwrap();
64        let discovered = discover(Some(dir.path())).unwrap();
65        assert_eq!(discovered.dir, dir.path());
66        assert!(discovered.is_empty());
67    }
68
69    #[test]
70    fn probe_ignores_the_retired_toml_filename() {
71        let configs = probe(
72            PathBuf::from("/home/op/.reticulum"),
73            &world(&["/home/op/.reticulum/config.toml"]),
74        );
75        assert_eq!(configs.config, None);
76        assert!(configs.is_empty());
77    }
78
79    #[test]
80    fn probe_returns_only_the_extensionless_config() {
81        let configs = probe(
82            PathBuf::from("/etc/reticulum"),
83            &world(&["/etc/reticulum/config", "/etc/reticulum/config.toml"]),
84        );
85        assert_eq!(configs.config, Some(PathBuf::from("/etc/reticulum/config")));
86    }
87}