openapi_nexus_config/
loader.rs

1//! Configuration file loader
2
3use std::error::Error;
4use std::fmt;
5use std::fs;
6use std::io;
7use std::path::{Path, PathBuf};
8
9use crate::config::ConfigFile;
10use toml;
11
12/// Configuration file loader
13pub struct ConfigLoader;
14
15impl ConfigLoader {
16    /// Discover and load configuration file from standard search paths
17    ///
18    /// Search order:
19    /// 1. ./openapi-nexus-config.toml
20    /// 2. ./.openapi-nexus-config.toml
21    /// 3. ~/.config/openapi-nexus/config.toml
22    /// 4. /etc/openapi-nexus/config.toml
23    pub fn discover_config_file() -> Option<PathBuf> {
24        // 1. Current directory - openapi-nexus-config.toml
25        let current_dir_file = Path::new("openapi-nexus-config.toml");
26        if current_dir_file.exists() {
27            return Some(current_dir_file.to_path_buf());
28        }
29
30        // 2. Current directory - .openapi-nexus-config.toml
31        let hidden_file = Path::new(".openapi-nexus-config.toml");
32        if hidden_file.exists() {
33            return Some(hidden_file.to_path_buf());
34        }
35
36        // 3. User config directory - OS-specific config directory
37        // e.g., ~/.config/openapi-nexus/config.toml on Linux
38        if let Some(config_dir) = dirs::config_dir() {
39            let user_config = config_dir.join("openapi-nexus").join("config.toml");
40            if user_config.exists() {
41                return Some(user_config);
42            }
43        }
44
45        // 4. System config directory - /etc/openapi-nexus/config.toml
46        let system_config = Path::new("/etc/openapi-nexus/config.toml");
47        if system_config.exists() {
48            return Some(system_config.to_path_buf());
49        }
50
51        None
52    }
53
54    /// Load configuration from a specific file path
55    pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<ConfigFile, LoadError> {
56        let content = fs::read_to_string(path.as_ref()).map_err(|e| LoadError::FileRead {
57            path: path.as_ref().to_path_buf(),
58            source: e,
59        })?;
60
61        toml::from_str(&content).map_err(|e| LoadError::Parse {
62            path: path.as_ref().to_path_buf(),
63            source: e,
64        })
65    }
66
67    /// Load configuration from discovered file or return default
68    pub fn load_or_default() -> ConfigFile {
69        Self::discover_config_file()
70            .and_then(|path| Self::load_from_file(&path).ok())
71            .unwrap_or_default()
72    }
73}
74
75/// Configuration loading errors
76#[derive(Debug)]
77pub enum LoadError {
78    FileRead {
79        path: PathBuf,
80        source: io::Error,
81    },
82    Parse {
83        path: PathBuf,
84        source: toml::de::Error,
85    },
86}
87
88impl fmt::Display for LoadError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        match self {
91            LoadError::FileRead { path, source } => {
92                write!(f, "Failed to read config file at {:?}: {}", path, source)
93            }
94            LoadError::Parse { path, source } => {
95                write!(f, "Failed to parse config file at {:?}: {}", path, source)
96            }
97        }
98    }
99}
100
101impl Error for LoadError {
102    fn source(&self) -> Option<&(dyn Error + 'static)> {
103        match self {
104            LoadError::FileRead { source, .. } => Some(source),
105            LoadError::Parse { source, .. } => Some(source),
106        }
107    }
108}