1use crate::value::{Map, Value};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum FileFormat {
8 Ini,
9 Json,
10 Yaml,
11 Toml,
12 Ron,
13}
14
15impl FileFormat {
16 pub fn from_extension(extension: &str) -> Option<Self> {
18 match extension {
19 "ini" => Some(FileFormat::Ini),
20 "json" => Some(FileFormat::Json),
21 "yaml" => Some(FileFormat::Yaml),
22 "toml" => Some(FileFormat::Toml),
23 "ron" => Some(FileFormat::Ron),
24 _ => None,
25 }
26 }
27}
28
29impl std::fmt::Display for FileFormat {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 FileFormat::Ini => write!(f, "ini"),
33 FileFormat::Json => write!(f, "json"),
34 FileFormat::Yaml => write!(f, "yaml"),
35 FileFormat::Toml => write!(f, "toml"),
36 FileFormat::Ron => write!(f, "ron"),
37 }
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct File {
44 pub path: String,
45 pub format: FileFormat,
46 pub content: String,
47}
48
49impl File {
50 pub fn new(path: String, format: FileFormat, content: String) -> Self {
52 File {
53 path,
54 format,
55 content,
56 }
57 }
58
59 pub fn new_str(path: &str, format: FileFormat, content: &str) -> Self {
61 File {
62 path: path.to_string(),
63 format,
64 content: content.to_string(),
65 }
66 }
67
68 #[cfg(feature = "read_file")]
70 pub fn from_path(path: String) -> Result<Self, String> {
71 let extension = path
72 .rsplit_once('.')
73 .and_then(|(_, ext)| if ext.is_empty() { None } else { Some(ext) })
74 .ok_or_else(|| format!("Failed to get file extension from {}", path))?;
75 let format = FileFormat::from_extension(extension)
76 .ok_or_else(|| format!("Unsupported file extension: {}", extension))?;
77
78 let content = std::fs::read_to_string(&path)
79 .map_err(|e| format!("Failed to read file {}: {}", path, e))?;
80
81 Ok(File::new(path.clone(), format, content))
82 }
83
84 #[cfg(feature = "read_file")]
86 pub fn from_path_format(path: String, format: FileFormat) -> Result<Self, String> {
87 let content = std::fs::read_to_string(&path)
88 .map_err(|e| format!("Failed to read file {}: {}", path, e))?;
89
90 Ok(File::new(path.clone(), format, content))
91 }
92
93 pub fn parse(&self) -> Result<Map<String, Value>, String> {
95 match self.format {
96 FileFormat::Ini => {
97 #[cfg(feature = "ini")]
98 {
99 crate::format::ini::deserialize(self.content.clone())
100 }
101
102 #[cfg(not(feature = "ini"))]
103 Err("INI format feature is not enabled".to_string())
104 }
105 FileFormat::Json => {
106 #[cfg(feature = "json")]
107 {
108 crate::format::json::deserialize(self.content.clone())
109 }
110
111 #[cfg(not(feature = "json"))]
112 Err("JSON format feature is not enabled".to_string())
113 }
114 FileFormat::Yaml => {
115 #[cfg(feature = "yaml")]
116 {
117 crate::format::yaml::deserialize(self.content.clone())
118 }
119
120 #[cfg(not(feature = "yaml"))]
121 Err("YAML format feature is not enabled".to_string())
122 }
123 FileFormat::Toml => {
124 #[cfg(feature = "toml")]
125 {
126 crate::format::toml::deserialize(self.content.clone())
127 }
128
129 #[cfg(not(feature = "toml"))]
130 Err("TOML format feature is not enabled".to_string())
131 }
132 FileFormat::Ron => {
133 #[cfg(feature = "ron")]
134 {
135 crate::format::ron::deserialize(self.content.clone())
136 }
137
138 #[cfg(not(feature = "ron"))]
139 Err("RON format feature is not enabled".to_string())
140 }
141 }
142 }
143}
144
145#[cfg(test)]
146mod test {
147 use super::*;
148
149 #[test]
150 fn test_file_new() {
151 let path = "test.json".to_string();
152 let format = FileFormat::Json;
153 let content = r#"{"key": "value"}"#.to_string();
154 let file = File::new(path.clone(), format.clone(), content.clone());
155 assert_eq!(file.path, path);
156 assert_eq!(file.format, format);
157 assert_eq!(file.content, content);
158 }
159
160 #[test]
161 fn test_file_new_str() {
162 let path = "test.json";
163 let format = FileFormat::Json;
164 let content = r#"{"key": "value"}"#;
165 let file = File::new_str(path, format.clone(), content);
166 assert_eq!(file.path, path);
167 assert_eq!(file.format, format);
168 assert_eq!(file.content, content);
169 }
170
171 #[test]
172 fn test_file_format_from_extension() {
173 assert_eq!(FileFormat::from_extension("ini"), Some(FileFormat::Ini));
174 assert_eq!(FileFormat::from_extension("json"), Some(FileFormat::Json));
175 assert_eq!(FileFormat::from_extension("yaml"), Some(FileFormat::Yaml));
176 assert_eq!(FileFormat::from_extension("toml"), Some(FileFormat::Toml));
177 assert_eq!(FileFormat::from_extension("ron"), Some(FileFormat::Ron));
178 assert_eq!(FileFormat::from_extension("txt"), None);
179 }
180
181 #[test]
182 fn test_file_format_display() {
183 assert_eq!(format!("{}", FileFormat::Ini), "ini");
184 assert_eq!(format!("{}", FileFormat::Json), "json");
185 assert_eq!(format!("{}", FileFormat::Yaml), "yaml");
186 assert_eq!(format!("{}", FileFormat::Toml), "toml");
187 assert_eq!(format!("{}", FileFormat::Ron), "ron");
188 }
189
190 #[test]
191 #[cfg(feature = "read_file")]
192 fn test_file_from_path() {
193 let path = "test.json".to_string();
194 let format = FileFormat::Json;
195 let content = r#"{"key": "value"}"#.to_string();
196 std::fs::write(&path, &content).unwrap();
197 let file = File::from_path(path.clone()).unwrap();
198 assert_eq!(file.path, path);
199 assert_eq!(file.format, format);
200 assert_eq!(file.content, content);
201 std::fs::remove_file(path.clone()).unwrap();
202
203 let file = File::from_path("test.json".to_string());
204 assert!(file.is_err());
205
206 let file = File::from_path("test_yaml.".to_string());
207 assert!(file.is_err());
208
209 let file = File::from_path("test.txt".to_string());
210 assert!(file.is_err());
211 }
212
213 #[test]
214 #[cfg(feature = "read_file")]
215 fn test_file_from_path_format() {
216 let path = "test2.json".to_string();
217 let format = FileFormat::Json;
218 let content = r#"{"key": "value"}"#.to_string();
219 std::fs::write(&path, &content).unwrap();
220 let file = File::from_path_format(path.clone(), format.clone()).unwrap();
221 assert_eq!(file.path, path);
222 assert_eq!(file.format, format);
223 assert_eq!(file.content, content);
224 std::fs::remove_file(path.clone()).unwrap();
225
226 let file = File::from_path_format(path.clone(), FileFormat::Yaml);
227 assert!(file.is_err());
228 }
229
230 mod formats {
231 use super::*;
232
233 #[test]
234 #[cfg(feature = "ini")]
235 fn test_parse_ini() {
236 let path = "test.ini".to_string();
237 let format = FileFormat::Ini;
238 let content = r#"[section]
239key: value"#
240 .to_string();
241 let file = File::new(path.clone(), format.clone(), content.clone());
242 let result = file.parse();
243 assert!(result.is_ok());
244 }
245
246 #[test]
247 #[cfg(not(feature = "ini"))]
248 fn test_parse_ini_fail() {
249 let path = "test.ini".to_string();
250 let format = FileFormat::Ini;
251 let content = r#"[section]
252key: value"#
253 .to_string();
254 let file = File::new(path.clone(), format.clone(), content.clone());
255 let result = file.parse();
256 assert!(result.is_err());
257 }
258
259 #[test]
260 #[cfg(feature = "json")]
261 fn test_parse_json() {
262 let path = "test.json".to_string();
263 let format = FileFormat::Json;
264 let content = r#"{"key": "value"}"#.to_string();
265 let file = File::new(path.clone(), format.clone(), content.clone());
266 let result = file.parse();
267 assert!(result.is_ok());
268 }
269
270 #[test]
271 #[cfg(not(feature = "json"))]
272 fn test_parse_json_fail() {
273 let path = "test.json".to_string();
274 let format = FileFormat::Json;
275 let content = r#"{"key": "value"}"#.to_string();
276 let file = File::new(path.clone(), format.clone(), content.clone());
277 let result = file.parse();
278 assert!(result.is_err());
279 }
280
281 #[test]
282 #[cfg(feature = "yaml")]
283 fn test_parse_yaml() {
284 let path = "test.yaml".to_string();
285 let format = FileFormat::Yaml;
286 let content = r#"key: value"#.to_string();
287 let file = File::new(path.clone(), format.clone(), content.clone());
288 let result = file.parse();
289 assert!(result.is_ok());
290 }
291
292 #[test]
293 #[cfg(not(feature = "yaml"))]
294 fn test_parse_yaml_fail() {
295 let path = "test.yaml".to_string();
296 let format = FileFormat::Yaml;
297 let content = r#"key: value"#.to_string();
298 let file = File::new(path.clone(), format.clone(), content.clone());
299 let result = file.parse();
300 assert!(result.is_err());
301 }
302
303 #[test]
304 #[cfg(feature = "toml")]
305 fn test_parse_toml() {
306 let path = "test.toml".to_string();
307 let format = FileFormat::Toml;
308 let content = r#"key = "value""#.to_string();
309 let file = File::new(path.clone(), format.clone(), content.clone());
310 let result = file.parse();
311 assert!(result.is_ok());
312 }
313
314 #[test]
315 #[cfg(not(feature = "toml"))]
316 fn test_parse_toml_fail() {
317 let path = "test.toml".to_string();
318 let format = FileFormat::Toml;
319 let content = r#"key = "value""#.to_string();
320 let file = File::new(path.clone(), format.clone(), content.clone());
321 let result = file.parse();
322 assert!(result.is_err());
323 }
324
325 #[test]
326 #[cfg(feature = "ron")]
327 fn test_parse_ron() {
328 let path = "test.ron".to_string();
329 let format = FileFormat::Ron;
330 let content = r#"(key: "value")"#.to_string();
331 let file = File::new(path.clone(), format.clone(), content.clone());
332 let result = file.parse();
333 assert!(result.is_ok());
334 }
335
336 #[test]
337 #[cfg(not(feature = "ron"))]
338 fn test_parse_ron_fail() {
339 let path = "test.ron".to_string();
340 let format = FileFormat::Ron;
341 let content = r#"(key: "value")"#.to_string();
342 let file = File::new(path.clone(), format.clone(), content.clone());
343 let result = file.parse();
344 assert!(result.is_err());
345 }
346 }
347}