armature_config/
loader.rs1use crate::{ConfigError, Result};
4use serde_json::Value;
5use std::fs;
6use std::path::Path;
7
8#[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
27pub struct ConfigLoader {
29 format: FileFormat,
30}
31
32impl ConfigLoader {
33 pub fn new(format: FileFormat) -> Self {
34 Self { format }
35 }
36
37 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 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 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 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}