armature_config/
loader.rs

1// Configuration file loaders
2
3use crate::{ConfigError, Result};
4use serde_json::Value;
5use std::fs;
6use std::path::Path;
7
8/// Supported configuration file formats
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum FileFormat {
11    Json,
12    Toml,
13    Env,
14}
15
16impl FileFormat {
17    pub fn from_extension(ext: &str) -> Option<Self> {
18        match ext.to_lowercase().as_str() {
19            "json" => Some(FileFormat::Json),
20            "toml" => Some(FileFormat::Toml),
21            "env" => Some(FileFormat::Env),
22            _ => None,
23        }
24    }
25}
26
27/// Configuration file loader
28pub struct ConfigLoader {
29    format: FileFormat,
30}
31
32impl ConfigLoader {
33    pub fn new(format: FileFormat) -> Self {
34        Self { format }
35    }
36
37    /// Auto-detect format from file extension
38    pub fn auto(path: &str) -> Result<Self> {
39        let path_obj = Path::new(path);
40        let ext = path_obj
41            .extension()
42            .and_then(|s| s.to_str())
43            .ok_or_else(|| ConfigError::LoadError("No file extension found".to_string()))?;
44
45        let format = FileFormat::from_extension(ext)
46            .ok_or_else(|| ConfigError::LoadError(format!("Unsupported format: {}", ext)))?;
47
48        Ok(Self::new(format))
49    }
50
51    /// Load configuration from file
52    pub fn load_file(&self, path: &str) -> Result<Value> {
53        let content = fs::read_to_string(path)
54            .map_err(|e| ConfigError::LoadError(format!("Failed to read file: {}", e)))?;
55
56        self.parse(&content)
57    }
58
59    /// Parse configuration from string
60    pub fn parse(&self, content: &str) -> Result<Value> {
61        match self.format {
62            FileFormat::Json => self.parse_json(content),
63            FileFormat::Toml => self.parse_toml(content),
64            FileFormat::Env => self.parse_env(content),
65        }
66    }
67
68    fn parse_json(&self, content: &str) -> Result<Value> {
69        serde_json::from_str(content)
70            .map_err(|e| ConfigError::ParseError(format!("JSON parse error: {}", e)))
71    }
72
73    fn parse_toml(&self, content: &str) -> Result<Value> {
74        let toml_value: toml::Value = toml::from_str(content)
75            .map_err(|e| ConfigError::ParseError(format!("TOML parse error: {}", e)))?;
76
77        // Convert TOML value to JSON value
78        let json_str = serde_json::to_string(&toml_value)
79            .map_err(|e| ConfigError::SerializationError(e.to_string()))?;
80
81        serde_json::from_str(&json_str)
82            .map_err(|e| ConfigError::ParseError(format!("TOML to JSON conversion error: {}", e)))
83    }
84
85    fn parse_env(&self, content: &str) -> Result<Value> {
86        let mut map = serde_json::Map::new();
87
88        for line in content.lines() {
89            let line = line.trim();
90            if line.is_empty() || line.starts_with('#') {
91                continue;
92            }
93
94            if let Some((key, value)) = line.split_once('=') {
95                let key = key.trim();
96                let value = value.trim().trim_matches('"').trim_matches('\'');
97                map.insert(key.to_string(), Value::String(value.to_string()));
98            }
99        }
100
101        Ok(Value::Object(map))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_parse_json() {
111        let loader = ConfigLoader::new(FileFormat::Json);
112        let json = r#"{"key": "value", "number": 42}"#;
113
114        let result = loader.parse(json).unwrap();
115        assert!(result.is_object());
116    }
117
118    #[test]
119    fn test_parse_toml() {
120        let loader = ConfigLoader::new(FileFormat::Toml);
121        let toml = r#"
122            key = "value"
123            number = 42
124        "#;
125
126        let result = loader.parse(toml).unwrap();
127        assert!(result.is_object());
128    }
129
130    #[test]
131    fn test_parse_env() {
132        let loader = ConfigLoader::new(FileFormat::Env);
133        let env = r#"
134            KEY=value
135            NUMBER=42
136            # Comment
137            QUOTED="quoted value"
138        "#;
139
140        let result = loader.parse(env).unwrap();
141        assert!(result.is_object());
142    }
143
144    #[test]
145    fn test_format_detection() {
146        assert_eq!(FileFormat::from_extension("json"), Some(FileFormat::Json));
147        assert_eq!(FileFormat::from_extension("toml"), Some(FileFormat::Toml));
148        assert_eq!(FileFormat::from_extension("env"), Some(FileFormat::Env));
149        assert_eq!(FileFormat::from_extension("unknown"), None);
150    }
151}