use crate::{Result, XbergError};
use std::path::Path;
use super::core::ExtractionConfig;
impl ExtractionConfig {
pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let content = std::fs::read_to_string(path)
.map_err(|e| XbergError::validation(format!("Failed to read config file {}: {}", path.display(), e)))?;
toml::from_str(&content)
.map_err(|e| XbergError::validation(format!("Invalid TOML in {}: {}", path.display(), e)))
}
pub fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let content = std::fs::read_to_string(path)
.map_err(|e| XbergError::validation(format!("Failed to read config file {}: {}", path.display(), e)))?;
serde_yaml_ng::from_str(&content)
.map_err(|e| XbergError::validation(format!("Invalid YAML in {}: {}", path.display(), e)))
}
pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let content = std::fs::read_to_string(path)
.map_err(|e| XbergError::validation(format!("Failed to read config file {}: {}", path.display(), e)))?;
serde_json::from_str(&content)
.map_err(|e| XbergError::validation(format!("Invalid JSON in {}: {}", path.display(), e)))
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let extension = path.extension().and_then(|ext| ext.to_str()).ok_or_else(|| {
XbergError::validation(format!(
"Cannot determine file format: no extension found in {}",
path.display()
))
})?;
match extension.to_lowercase().as_str() {
"toml" => Self::from_toml_file(path),
"yaml" | "yml" => Self::from_yaml_file(path),
"json" => Self::from_json_file(path),
other => Err(XbergError::validation(format!(
"Unsupported config file format: .{}. Supported formats: .toml, .yaml, .json",
other
))),
}
}
pub fn discover() -> Result<Option<Self>> {
let mut current = std::env::current_dir().map_err(crate::XbergError::from)?;
loop {
let xberg_toml = current.join("xberg.toml");
if xberg_toml.exists() {
return Ok(Some(Self::from_toml_file(xberg_toml)?));
}
if let Some(parent) = current.parent() {
current = parent.to_path_buf();
} else {
break;
}
}
if let Some(config_dir) = dirs::config_dir()
&& let Some(config) = Self::find_config_in_dir(&config_dir.join("xberg"))?
{
return Ok(Some(config));
}
Ok(None)
}
fn find_config_in_dir(dir: &Path) -> Result<Option<Self>> {
const CONFIG_BASENAMES: [&str; 4] = ["xberg.toml", "xberg.yaml", "xberg.yml", "xberg.json"];
for basename in CONFIG_BASENAMES {
let candidate = dir.join(basename);
if candidate.exists() {
return Ok(Some(Self::from_file(candidate)?));
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_config_in_dir_returns_none_when_absent() {
let dir = tempfile::tempdir().unwrap();
let found = ExtractionConfig::find_config_in_dir(dir.path()).unwrap();
assert!(found.is_none(), "empty dir must yield no config");
}
#[test]
fn find_config_in_dir_loads_yaml_and_json() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("xberg.json"), "{}").unwrap();
assert!(
ExtractionConfig::find_config_in_dir(dir.path()).unwrap().is_some(),
"xberg.json must be discovered"
);
std::fs::remove_file(dir.path().join("xberg.json")).unwrap();
std::fs::write(dir.path().join("xberg.yaml"), "use_cache: true\n").unwrap();
assert!(
ExtractionConfig::find_config_in_dir(dir.path()).unwrap().is_some(),
"xberg.yaml must be discovered"
);
}
#[test]
fn find_config_in_dir_prefers_toml_over_other_formats() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("xberg.toml"), "use_cache = true\n").unwrap();
std::fs::write(dir.path().join("xberg.json"), "not valid json").unwrap();
let found = ExtractionConfig::find_config_in_dir(dir.path()).unwrap();
assert!(found.is_some(), "xberg.toml must win over xberg.json");
}
}