axl_config 0.1.0

Configuration loading for axl accessibility linting.
Documentation
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, Deserialize, Default)]
pub struct AxlConfig {
    #[serde(default)]
    pub extends: Vec<String>,
    #[serde(default)]
    pub components: HashMap<String, String>,
    #[serde(default)]
    pub rules: HashMap<String, serde_json::Value>,
    #[serde(default)]
    pub ignore: Vec<String>,
}

impl AxlConfig {
    pub fn load(path: Option<&str>) -> Result<Self> {
        let selected = path.unwrap_or("axl.config.json");
        if !Path::new(selected).exists() {
            return Ok(Self::default());
        }
        let raw = fs::read_to_string(selected)?;
        let cfg: AxlConfig = serde_json::from_str(&raw)?;
        Ok(cfg)
    }
}

#[cfg(test)]
mod tests {
    use super::AxlConfig;
    use std::fs;

    #[test]
    fn returns_default_when_file_missing() {
        let path = std::env::temp_dir().join("axl_config_missing.json");
        if path.exists() {
            fs::remove_file(&path).expect("cleanup existing file");
        }
        let cfg = AxlConfig::load(Some(path.to_string_lossy().as_ref())).expect("load should work");
        assert!(cfg.extends.is_empty());
        assert!(cfg.ignore.is_empty());
    }

    #[test]
    fn loads_config_from_json_file() {
        let path = std::env::temp_dir().join(format!(
            "axl_config_{}.json",
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .expect("clock should be after epoch")
                .as_nanos()
        ));
        fs::write(
            &path,
            r#"{
  "extends": ["recommended"],
  "components": { "Link": "a" },
  "rules": { "focus/tabindex-positive": "warn" },
  "ignore": ["**/*.test.tsx"]
}"#,
        )
        .expect("write config file");

        let cfg = AxlConfig::load(Some(path.to_string_lossy().as_ref())).expect("config should parse");
        assert_eq!(cfg.extends, vec!["recommended"]);
        assert_eq!(cfg.components.get("Link").map(String::as_str), Some("a"));
        assert!(cfg.rules.contains_key("focus/tabindex-positive"));
        assert_eq!(cfg.ignore, vec!["**/*.test.tsx"]);

        let _ = fs::remove_file(path);
    }
}