use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const PROJECT_CONFIG_FILENAME: &str = ".trusty-search.yaml";
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ProjectConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exclude: Option<Vec<String>>,
}
impl ProjectConfig {
pub fn load(dir: &Path) -> anyhow::Result<Option<Self>> {
let path = dir.join(PROJECT_CONFIG_FILENAME);
if !path.exists() {
return Ok(None);
}
let raw = std::fs::read_to_string(&path)
.map_err(|e| anyhow::anyhow!("failed to read {}: {e}", path.display()))?;
let cfg: Self = serde_yml::from_str(&raw)
.map_err(|e| anyhow::anyhow!("failed to parse {}: {e}", path.display()))?;
Ok(Some(cfg))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_load_absent() {
let tmp = tempdir().unwrap();
let res = ProjectConfig::load(tmp.path()).unwrap();
assert!(res.is_none(), "missing config file must return Ok(None)");
}
#[test]
fn test_load_name_only() {
let tmp = tempdir().unwrap();
fs::write(tmp.path().join(PROJECT_CONFIG_FILENAME), "name: foo\n").unwrap();
let cfg = ProjectConfig::load(tmp.path())
.unwrap()
.expect("config present");
assert_eq!(cfg.name.as_deref(), Some("foo"));
assert!(cfg.path.is_none());
assert!(cfg.exclude.is_none());
}
#[test]
fn test_load_full() {
let tmp = tempdir().unwrap();
fs::write(
tmp.path().join(PROJECT_CONFIG_FILENAME),
r#"
name: cto
path: app
exclude:
- data/
- docs/
- "*.db"
"#,
)
.unwrap();
let cfg = ProjectConfig::load(tmp.path())
.unwrap()
.expect("config present");
assert_eq!(cfg.name.as_deref(), Some("cto"));
assert_eq!(cfg.path, Some(PathBuf::from("app")));
assert_eq!(
cfg.exclude,
Some(vec![
"data/".to_string(),
"docs/".to_string(),
"*.db".to_string(),
])
);
}
#[test]
fn test_load_malformed() {
let tmp = tempdir().unwrap();
fs::write(
tmp.path().join(PROJECT_CONFIG_FILENAME),
"name: [unclosed\n : :",
)
.unwrap();
let res = ProjectConfig::load(tmp.path());
assert!(res.is_err(), "malformed yaml must return Err, not panic");
}
}