diffx_core/
lib.rs

1use serde::Serialize;
2use serde_json::Value;
3use regex::Regex;
4use std::collections::HashMap;
5// use ini::Ini;
6use anyhow::{Result, anyhow};
7use quick_xml::de::from_str;
8use csv::ReaderBuilder;
9
10#[derive(Debug, PartialEq, Serialize)]
11pub enum DiffResult {
12    Added(String, Value),
13    Removed(String, Value),
14    Modified(String, Value, Value),
15    TypeChanged(String, Value, Value),
16}
17
18pub fn diff(
19    v1: &Value,
20    v2: &Value,
21    ignore_keys_regex: Option<&Regex>,
22    epsilon: Option<f64>,
23    array_id_key: Option<&str>,
24) -> Vec<DiffResult> {
25    let mut results = Vec::new();
26
27    // Handle root level type or value change first
28    if !values_are_equal(v1, v2, epsilon) {
29        let type_match = match (v1, v2) {
30            (Value::Null, Value::Null) => true,
31            (Value::Bool(_), Value::Bool(_)) => true,
32            (Value::Number(_), Value::Number(_)) => true,
33            (Value::String(_), Value::String(_)) => true,
34            (Value::Array(_), Value::Array(_)) => true,
35            (Value::Object(_), Value::Object(_)) => true,
36            _ => false,
37        };
38
39        if !type_match {
40            results.push(DiffResult::TypeChanged("".to_string(), v1.clone(), v2.clone()));
41            return results; // If root type changed, no further diffing needed
42        } else if v1.is_object() && v2.is_object() {
43            diff_objects("", v1.as_object().unwrap(), v2.as_object().unwrap(), &mut results, ignore_keys_regex, epsilon, array_id_key);
44        } else if v1.is_array() && v2.is_array() {
45            diff_arrays("", v1.as_array().unwrap(), v2.as_array().unwrap(), &mut results, ignore_keys_regex, epsilon, array_id_key);
46        } else {
47            // Simple value modification at root
48            results.push(DiffResult::Modified("".to_string(), v1.clone(), v2.clone()));
49            return results;
50        }
51    }
52
53    results
54}
55
56fn diff_recursive(
57    path: &str,
58    v1: &Value,
59    v2: &Value,
60    results: &mut Vec<DiffResult>,
61    ignore_keys_regex: Option<&Regex>,
62    epsilon: Option<f64>,
63    array_id_key: Option<&str>,
64) {
65    match (v1, v2) {
66        (Value::Object(map1), Value::Object(map2)) => {
67            diff_objects(path, map1, map2, results, ignore_keys_regex, epsilon, array_id_key);
68        }
69        (Value::Array(arr1), Value::Array(arr2)) => {
70            diff_arrays(path, arr1, arr2, results, ignore_keys_regex, epsilon, array_id_key);
71        }
72        _ => { /* Should not happen if called correctly from diff_objects/diff_arrays */ }
73    }
74}
75
76fn diff_objects(
77    path: &str,
78    map1: &serde_json::Map<String, Value>,
79    map2: &serde_json::Map<String, Value>,
80    results: &mut Vec<DiffResult>,
81    ignore_keys_regex: Option<&Regex>,
82    epsilon: Option<f64>,
83    array_id_key: Option<&str>,
84) {
85    // Check for modified or removed keys
86    for (key, value1) in map1 {
87        let current_path = if path.is_empty() { key.clone() } else { format!("{}.{}", path, key) };
88        if let Some(regex) = ignore_keys_regex {
89            if regex.is_match(key) {
90                continue;
91            }
92        }
93        match map2.get(key) {
94            Some(value2) => {
95                // Recurse for nested objects/arrays
96                if value1.is_object() && value2.is_object() || value1.is_array() && value2.is_array() {
97                    diff_recursive(&current_path, value1, value2, results, ignore_keys_regex, epsilon, array_id_key);
98                } else if !values_are_equal(value1, value2, epsilon) {
99                    let type_match = match (value1, value2) {
100                        (Value::Null, Value::Null) => true,
101                        (Value::Bool(_), Value::Bool(_)) => true,
102                        (Value::Number(_), Value::Number(_)) => true,
103                        (Value::String(_), Value::String(_)) => true,
104                        (Value::Array(_), Value::Array(_)) => true,
105                        (Value::Object(_), Value::Object(_)) => true,
106                        _ => false,
107                    };
108
109                    if !type_match {
110                        results.push(DiffResult::TypeChanged(current_path, value1.clone(), value2.clone()));
111                    } else {
112                        results.push(DiffResult::Modified(current_path, value1.clone(), value2.clone()));
113                    }
114                }
115            }
116            None => {
117                results.push(DiffResult::Removed(current_path, value1.clone()));
118            }
119        }
120    }
121
122    // Check for added keys
123    for (key, value2) in map2 {
124        if !map1.contains_key(key) {
125            let current_path = if path.is_empty() { key.clone() } else { format!("{}.{}", path, key) };
126            results.push(DiffResult::Added(current_path, value2.clone()));
127        }
128    }
129}
130
131fn diff_arrays(
132    path: &str,
133    arr1: &Vec<Value>,
134    arr2: &Vec<Value>,
135    results: &mut Vec<DiffResult>,
136    ignore_keys_regex: Option<&Regex>,
137    epsilon: Option<f64>,
138    array_id_key: Option<&str>,
139) {
140    if let Some(id_key) = array_id_key {
141        let mut map1: HashMap<Value, &Value> = HashMap::new();
142        let mut no_id_elements1: Vec<(usize, &Value)> = Vec::new();
143        for (i, val) in arr1.iter().enumerate() {
144            if let Some(id_val) = val.get(id_key) {
145                map1.insert(id_val.clone(), val);
146            } else {
147                no_id_elements1.push((i, val));
148            }
149        }
150
151        let mut map2: HashMap<Value, &Value> = HashMap::new();
152        let mut no_id_elements2: Vec<(usize, &Value)> = Vec::new();
153        for (i, val) in arr2.iter().enumerate() {
154            if let Some(id_val) = val.get(id_key) {
155                map2.insert(id_val.clone(), val);
156            } else {
157                no_id_elements2.push((i, val));
158            }
159        }
160
161        // Check for modified or removed elements
162        for (id_val, val1) in &map1 {
163            let current_path = format!("{}[{}={}]", path, id_key, id_val);
164            match map2.get(&id_val) {
165                Some(val2) => {
166                    // Recurse for nested objects/arrays
167                    if val1.is_object() && val2.is_object() || val1.is_array() && val2.is_array() {
168                        diff_recursive(&current_path, val1, val2, results, ignore_keys_regex, epsilon, array_id_key);
169                    } else if !values_are_equal(val1, val2, epsilon) {
170                        let type_match = match (val1, val2) {
171                            (Value::Null, Value::Null) => true,
172                            (Value::Bool(_), Value::Bool(_)) => true,
173                            (Value::Number(_), Value::Number(_)) => true,
174                            (Value::String(_), Value::String(_)) => true,
175                            (Value::Array(_), Value::Array(_)) => true,
176                            (Value::Object(_), Value::Object(_)) => true,
177                            _ => false,
178                        };
179
180                        if !type_match {
181                            results.push(DiffResult::TypeChanged(current_path, (*val1).clone(), (*val2).clone()));
182                        } else {
183                            results.push(DiffResult::Modified(current_path, (*val1).clone(), (*val2).clone()));
184                        }
185                    }
186                }
187                None => {
188                    results.push(DiffResult::Removed(current_path, (*val1).clone()));
189                }
190            }
191        }
192
193        // Check for added elements with ID
194        for (id_val, val2) in map2 {
195            if !map1.contains_key(&id_val) {
196                let current_path = format!("{}[{}={}]", path, id_key, id_val);
197                results.push(DiffResult::Added(current_path, val2.clone()));
198            }
199        }
200
201        // Handle elements without ID using index-based comparison
202        let max_len = no_id_elements1.len().max(no_id_elements2.len());
203        for i in 0..max_len {
204            match (no_id_elements1.get(i), no_id_elements2.get(i)) {
205                (Some((idx1, val1)), Some((_idx2, val2))) => {
206                    let current_path = format!("{}[{}]", path, idx1);
207                    if val1.is_object() && val2.is_object() || val1.is_array() && val2.is_array() {
208                        diff_recursive(&current_path, val1, val2, results, ignore_keys_regex, epsilon, array_id_key);
209                    } else if !values_are_equal(val1, val2, epsilon) {
210                        let type_match = match (val1, val2) {
211                            (Value::Null, Value::Null) => true,
212                            (Value::Bool(_), Value::Bool(_)) => true,
213                            (Value::Number(_), Value::Number(_)) => true,
214                            (Value::String(_), Value::String(_)) => true,
215                            (Value::Array(_), Value::Array(_)) => true,
216                            (Value::Object(_), Value::Object(_)) => true,
217                            _ => false,
218                        };
219
220                        if !type_match {
221                            results.push(DiffResult::TypeChanged(current_path, (*val1).clone(), (*val2).clone()));
222                        } else {
223                            results.push(DiffResult::Modified(current_path, (*val1).clone(), (*val2).clone()));
224                        }
225                    }
226                }
227                (Some((idx1, val1)), None) => {
228                    let current_path = format!("{}[{}]", path, idx1);
229                    results.push(DiffResult::Removed(current_path, (*val1).clone()));
230                }
231                (None, Some((idx2, val2))) => {
232                    let current_path = format!("{}[{}]", path, idx2);
233                    results.push(DiffResult::Added(current_path, (*val2).clone()));
234                }
235                (None, None) => break,
236            }
237        }
238    } else {
239        // Fallback to index-based comparison if no id_key is provided
240        let max_len = arr1.len().max(arr2.len());
241        for i in 0..max_len {
242            let current_path = format!("{}[{}]", path, i);
243            match (arr1.get(i), arr2.get(i)) {
244                (Some(val1), Some(val2)) => {
245                    // Recurse for nested objects/arrays within arrays
246                    if val1.is_object() && val2.is_object() || val1.is_array() && val2.is_array() {
247                        diff_recursive(&current_path, val1, val2, results, ignore_keys_regex, epsilon, array_id_key);
248                    } else if !values_are_equal(val1, val2, epsilon) {
249                        let type_match = match (val1, val2) {
250                            (Value::Null, Value::Null) => true,
251                            (Value::Bool(_), Value::Bool(_)) => true,
252                            (Value::Number(_), Value::Number(_)) => true,
253                            (Value::String(_), Value::String(_)) => true,
254                            (Value::Array(_), Value::Array(_)) => true,
255                            (Value::Object(_), Value::Object(_)) => true,
256                            _ => false,
257                        };
258
259                        if !type_match {
260                            results.push(DiffResult::TypeChanged(current_path, val1.clone(), val2.clone()));
261                        } else {
262                            results.push(DiffResult::Modified(current_path, val1.clone(), val2.clone()));
263                        }
264                    }
265                }
266                (Some(val1), None) => {
267                    results.push(DiffResult::Removed(current_path, val1.clone()));
268                }
269                (None, Some(val2)) => {
270                    results.push(DiffResult::Added(current_path, val2.clone()));
271                }
272                (None, None) => { /* Should not happen */ }
273            }
274        }
275    }
276}
277
278fn values_are_equal(v1: &Value, v2: &Value, epsilon: Option<f64>) -> bool {
279    if let (Some(e), Value::Number(n1), Value::Number(n2)) = (epsilon, v1, v2) {
280        if let (Some(f1), Some(f2)) = (n1.as_f64(), n2.as_f64()) {
281            return (f1 - f2).abs() < e;
282        }
283    }
284    v1 == v2
285}
286
287pub fn value_type_name(value: &Value) -> &str {
288    match value {
289        Value::Null => "Null",
290        Value::Bool(_) => "Boolean",
291        Value::Number(_) => "Number",
292        Value::String(_) => "String",
293        Value::Array(_) => "Array",
294        Value::Object(_) => "Object",
295    }
296}
297
298pub fn parse_ini(content: &str) -> Result<Value> {
299    use configparser::ini::Ini;
300    
301    let mut ini = Ini::new();
302    ini.read(content.to_string())
303        .map_err(|e| anyhow!("Failed to parse INI: {}", e))?;
304    
305    let mut root_map = serde_json::Map::new();
306
307    for section_name in ini.sections() {
308        let mut section_map = serde_json::Map::new();
309        
310        if let Some(section) = ini.get_map_ref().get(&section_name) {
311            for (key, value) in section {
312                if let Some(v) = value {
313                    section_map.insert(key.clone(), Value::String(v.clone()));
314                } else {
315                    section_map.insert(key.clone(), Value::Null);
316                }
317            }
318        }
319        
320        root_map.insert(section_name, Value::Object(section_map));
321    }
322
323    Ok(Value::Object(root_map))
324}
325
326pub fn parse_xml(content: &str) -> Result<Value> {
327    let value: Value = from_str(content)?;
328    Ok(value)
329}
330
331pub fn parse_csv(content: &str) -> Result<Value> {
332    let mut reader = ReaderBuilder::new().from_reader(content.as_bytes());
333    let mut records = Vec::new();
334
335    let headers = reader.headers()?.clone();
336    let has_headers = !headers.is_empty();
337
338    for result in reader.into_records() {
339        let record = result?;
340        if has_headers {
341            let mut obj = serde_json::Map::new();
342            for (i, header) in headers.iter().enumerate() {
343                if let Some(value) = record.get(i) {
344                    obj.insert(header.to_string(), Value::String(value.to_string()));
345                }
346            }
347            records.push(Value::Object(obj));
348        } else {
349            let mut arr = Vec::new();
350            for field in record.iter() {
351                arr.push(Value::String(field.to_string()));
352            }
353            records.push(Value::Array(arr));
354        }
355    }
356    Ok(Value::Array(records))
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use serde_json::json;
363
364    #[test]
365    fn test_diff_no_changes() {
366        let v1 = json!({ "a": 1, "b": 2 });
367        let v2 = json!({ "a": 1, "b": 2 });
368        let differences = diff(&v1, &v2, None, None, None);
369        assert!(differences.is_empty());
370    }
371
372    #[test]
373    fn test_diff_value_modified() {
374        let v1 = json!({ "a": 1, "b": 2 });
375        let v2 = json!({ "a": 1, "b": 3 });
376        let differences = diff(&v1, &v2, None, None, None);
377        assert_eq!(differences.len(), 1);
378        assert_eq!(differences[0], DiffResult::Modified("b".to_string(), json!(2), json!(3)));
379    }
380
381    #[test]
382    fn test_diff_key_added() {
383        let v1 = json!({ "a": 1 });
384        let v2 = json!({ "a": 1, "b": 2 });
385        let differences = diff(&v1, &v2, None, None, None);
386        assert_eq!(differences.len(), 1);
387        assert_eq!(differences[0], DiffResult::Added("b".to_string(), json!(2)));
388    }
389
390    #[test]
391    fn test_diff_key_removed() {
392        let v1 = json!({ "a": 1, "b": 2 });
393        let v2 = json!({ "a": 1 });
394        let differences = diff(&v1, &v2, None, None, None);
395        assert_eq!(differences.len(), 1);
396        assert_eq!(differences[0], DiffResult::Removed("b".to_string(), json!(2)));
397    }
398
399    #[test]
400    fn test_diff_type_changed() {
401        let v1 = json!({ "a": 1 });
402        let v2 = json!({ "a": "1" });
403        let differences = diff(&v1, &v2, None, None, None);
404        assert_eq!(differences.len(), 1);
405        assert_eq!(differences[0], DiffResult::TypeChanged("a".to_string(), json!(1), json!("1")));
406    }
407
408    #[test]
409    fn test_diff_nested_object_modified() {
410        let v1 = json!({ "a": { "b": 1 } });
411        let v2 = json!({ "a": { "b": 2 } });
412        let differences = diff(&v1, &v2, None, None, None);
413        assert_eq!(differences.len(), 1);
414        assert_eq!(differences[0], DiffResult::Modified("a.b".to_string(), json!(1), json!(2)));
415    }
416
417    #[test]
418    fn test_diff_array_element_added() {
419        let v1 = json!([1, 2]);
420        let v2 = json!([1, 2, 3]);
421        let differences = diff(&v1, &v2, None, None, None);
422        assert_eq!(differences.len(), 1);
423        assert_eq!(differences[0], DiffResult::Added("[2]".to_string(), json!(3)));
424    }
425
426    #[test]
427    fn test_diff_array_element_removed() {
428        let v1 = json!([1, 2, 3]);
429        let v2 = json!([1, 2]);
430        let differences = diff(&v1, &v2, None, None, None);
431        assert_eq!(differences.len(), 1);
432        assert_eq!(differences[0], DiffResult::Removed("[2]".to_string(), json!(3)));
433    }
434
435    #[test]
436    fn test_diff_array_element_modified() {
437        let v1 = json!([1, 2, 3]);
438        let v2 = json!([1, 2, 4]);
439        let differences = diff(&v1, &v2, None, None, None);
440        assert_eq!(differences.len(), 1);
441        assert_eq!(differences[0], DiffResult::Modified("[2]".to_string(), json!(3), json!(4)));
442    }
443
444    #[test]
445    fn test_diff_nested_array_element_modified() {
446        let v1 = json!({ "a": [1, 2, 3] });
447        let v2 = json!({ "a": [1, 2, 4] });
448        let differences = diff(&v1, &v2, None, None, None);
449        assert_eq!(differences.len(), 1);
450        assert_eq!(differences[0], DiffResult::Modified("a[2]".to_string(), json!(3), json!(4)));
451    }
452
453    #[test]
454    fn test_diff_root_type_changed() {
455        let v1 = json!(1);
456        let v2 = json!("1");
457        let differences = diff(&v1, &v2, None, None, None);
458        assert_eq!(differences.len(), 1);
459        assert_eq!(differences[0], DiffResult::TypeChanged("".to_string(), json!(1), json!("1")));
460    }
461
462    #[test]
463    fn test_diff_nested_object_and_array() {
464        let v1 = json!({
465            "config": {
466                "users": [
467                    {"id": 1, "name": "Alice"},
468                    {"id": 2, "name": "Bob"}
469                ],
470                "settings": {"theme": "dark"}
471            }
472        });
473        let v2 = json!({
474            "config": {
475                "users": [
476                    {"id": 1, "name": "Alice"},
477                    {"id": 2, "name": "Robert"},
478                    {"id": 3, "name": "Charlie"}
479                ],
480                "settings": {"theme": "light", "font_size": 12}
481            }
482        });
483        let differences = diff(&v1, &v2, None, None, None);
484        assert_eq!(differences.len(), 4);
485        assert!(differences.contains(&DiffResult::Modified("config.users[1].name".to_string(), json!("Bob"), json!("Robert"))));
486        assert!(differences.contains(&DiffResult::Added("config.users[2]".to_string(), json!({"id": 3, "name": "Charlie"}))));
487        assert!(differences.contains(&DiffResult::Modified("config.settings.theme".to_string(), json!("dark"), json!("light"))));
488        assert!(differences.contains(&DiffResult::Added("config.settings.font_size".to_string(), json!(12))));
489    }
490
491    #[test]
492    fn test_diff_empty_objects_and_arrays() {
493        let v1 = json!({
494            "empty_obj": {},
495            "empty_arr": [],
496            "data": "value"
497        });
498        let v2 = json!({
499            "empty_obj": {},
500            "empty_arr": [],
501            "data": "new_value"
502        });
503        let differences = diff(&v1, &v2, None, None, None);
504        assert_eq!(differences.len(), 1);
505        assert_eq!(differences[0], DiffResult::Modified("data".to_string(), json!("value"), json!("new_value")));
506    }
507
508    #[test]
509    fn test_diff_root_array_changes() {
510        let v1 = json!([
511            {"id": 1},
512            {"id": 2}
513        ]);
514        let v2 = json!([
515            {"id": 1},
516            {"id": 3},
517            {"id": 4}
518        ]);
519        let differences = diff(&v1, &v2, None, None, None);
520        assert_eq!(differences.len(), 2);
521        assert!(differences.contains(&DiffResult::Modified("[1].id".to_string(), json!(2), json!(3))));
522        assert!(differences.contains(&DiffResult::Added("[2]".to_string(), json!({"id": 4}))));
523    }
524
525    #[test]
526    fn test_diff_ignore_keys_regex() {
527        let v1 = json!({ "id": 1, "name": "Alice", "_timestamp": "abc" });
528        let v2 = json!({ "id": 2, "name": "Alice", "_timestamp": "def" });
529        let regex = Regex::new(r"^_.*").unwrap();
530        let differences = diff(&v1, &v2, Some(&regex), None, None);
531        assert_eq!(differences.len(), 1);
532        assert!(differences.contains(&DiffResult::Modified("id".to_string(), json!(1), json!(2))));
533
534        let v3 = json!({ "id": 1, "name": "Alice", "version": "1.0" });
535        let v4 = json!({ "id": 1, "name": "Bob", "version": "1.1" });
536        let regex_name = Regex::new(r"^name$").unwrap();
537        let differences_name = diff(&v3, &v4, Some(&regex_name), None, None);
538        assert_eq!(differences_name.len(), 1);
539        assert!(differences_name.contains(&DiffResult::Modified("version".to_string(), json!("1.0"), json!("1.1"))));
540    }
541
542    #[test]
543    fn test_diff_ignore_keys_regex_nested() {
544        let v1 = json!({ "data": { "id": 1, "_timestamp": "abc" } });
545        let v2 = json!({ "data": { "id": 2, "_timestamp": "def" } });
546        let regex = Regex::new(r"^_.*").unwrap();
547        let differences = diff(&v1, &v2, Some(&regex), None, None);
548        assert_eq!(differences.len(), 1);
549        assert!(differences.contains(&DiffResult::Modified("data.id".to_string(), json!(1), json!(2))));
550    }
551
552    #[test]
553    fn test_diff_epsilon_comparison() {
554        let v1 = json!({ "a": 1.0, "b": 2.000001 });
555        let v2 = json!({ "a": 1.0, "b": 2.000002 });
556        let epsilon = Some(0.00001);
557        let differences = diff(&v1, &v2, None, epsilon, None);
558        assert!(differences.is_empty());
559
560        let v3 = json!({ "a": 1.0, "b": 2.00001 });
561        let v4 = json!({ "a": 1.0, "b": 2.00003 });
562        let epsilon_large = Some(0.00001);
563        let differences_large = diff(&v3, &v4, None, epsilon_large, None);
564        assert_eq!(differences_large.len(), 1);
565        assert_eq!(differences_large[0], DiffResult::Modified("b".to_string(), json!(2.00001), json!(2.00003)));
566    }
567
568    #[test]
569    fn test_diff_epsilon_comparison_type_mismatch() {
570        let v1 = json!({ "a": 1.0 });
571        let v2 = json!({ "a": "1.0" });
572        let epsilon = Some(0.00001);
573        let differences = diff(&v1, &v2, None, epsilon, None);
574        assert_eq!(differences.len(), 1);
575        assert_eq!(differences[0], DiffResult::TypeChanged("a".to_string(), json!(1.0), json!("1.0")));
576    }
577
578    #[test]
579    fn test_diff_array_id_key_modified() {
580        let v1 = json!([
581            {"id": 1, "value": "a"},
582            {"id": 2, "value": "b"}
583        ]);
584        let v2 = json!([
585            {"id": 2, "value": "c"},
586            {"id": 1, "value": "a"}
587        ]);
588        let differences = diff(&v1, &v2, None, None, Some("id"));
589        assert_eq!(differences.len(), 1);
590        assert!(differences.contains(&DiffResult::Modified("[id=2].value".to_string(), json!("b"), json!("c"))));
591    }
592
593    #[test]
594    fn test_diff_array_id_key_added_removed() {
595        let v1 = json!([
596            {"id": 1, "value": "a"},
597            {"id": 2, "value": "b"}
598        ]);
599        let v2 = json!([
600            {"id": 1, "value": "a"},
601            {"id": 3, "value": "c"}
602        ]);
603        let differences = diff(&v1, &v2, None, None, Some("id"));
604        assert_eq!(differences.len(), 2);
605        assert!(differences.contains(&DiffResult::Removed("[id=2]".to_string(), json!({"id": 2, "value": "b"}))));
606        assert!(differences.contains(&DiffResult::Added("[id=3]".to_string(), json!({"id": 3, "value": "c"}))));
607    }
608
609    #[test]
610    fn test_diff_array_id_key_nested_change() {
611        let v1 = json!([
612            {"id": 1, "data": {"name": "A"}},
613            {"id": 2, "data": {"name": "B"}}
614        ]);
615        let v2 = json!([
616            {"id": 2, "data": {"name": "C"}},
617            {"id": 1, "data": {"name": "A"}}
618        ]);
619        let differences = diff(&v1, &v2, None, None, Some("id"));
620        assert_eq!(differences.len(), 1);
621        assert!(differences.contains(&DiffResult::Modified("[id=2].data.name".to_string(), json!("B"), json!("C"))));
622    }
623
624    #[test]
625    fn test_diff_array_id_key_no_id_in_element() {
626        let v1 = json!([
627            {"id": 1, "value": "a"},
628            {"value": "b"}
629        ]);
630        let v2 = json!([
631            {"id": 1, "value": "a"},
632            {"value": "c"}
633        ]);
634        // Elements without the id_key should be compared by index
635        let differences = diff(&v1, &v2, None, None, Some("id"));
636        assert_eq!(differences.len(), 1);
637        assert!(differences.contains(&DiffResult::Modified("[1].value".to_string(), json!("b"), json!("c"))));
638    }
639
640    #[test]
641    fn test_diff_array_id_key_with_epsilon() {
642        let v1 = json!([
643            {"id": 1, "value": 1.000001},
644            {"id": 2, "value": 2.0}
645        ]);
646        let v2 = json!([
647            {"id": 1, "value": 1.000002},
648            {"id": 2, "value": 2.0}
649        ]);
650        let epsilon = Some(0.00001);
651        let differences = diff(&v1, &v2, None, epsilon, Some("id"));
652        assert!(differences.is_empty());
653    }
654
655    #[test]
656    fn test_parse_ini() {
657        let ini_content = r#"
658[section1]
659key1 = value1
660key2 = value2
661
662[section2]
663key3 = value3
664"#;
665        let expected = json!({
666            "section1": {
667                "key1": "value1",
668                "key2": "value2"
669            },
670            "section2": {
671                "key3": "value3"
672            }
673        });
674        let parsed = parse_ini(ini_content).unwrap();
675        assert_eq!(parsed, expected);
676    }
677
678    #[test]
679    fn test_parse_ini_global_section() {
680        let ini_content = r#"
681key_global = value_global
682[section1]
683key1 = value1
684"#;
685        let expected = json!({
686            "key_global": "value_global",
687            "section1": {
688                "key1": "value1"
689            }
690        });
691        let parsed = parse_ini(ini_content).unwrap();
692        assert_eq!(parsed, expected);
693    }
694
695    #[test]
696    fn test_parse_xml() {
697        let xml_content = r#"
698<root>
699    <item id="1">value1</item>
700    <item id="2">value2</item>
701</root>
702"#;
703        let expected = json!({
704            "root": {
705                "item": [
706                    {
707                        "#text": "value1",
708                        "@id": "1"
709                    },
710                    {
711                        "#text": "value2",
712                        "@id": "2"
713                    }
714                ]
715            }
716        });
717        let parsed = parse_xml(xml_content).unwrap();
718        assert_eq!(parsed, expected);
719    }
720
721    #[test]
722    fn test_parse_xml_single_element() {
723        let xml_content = r#"
724<data>
725    <name>test</name>
726</data>
727"#;
728        let expected = json!({
729            "data": {
730                "name": "test"
731            }
732        });
733        let parsed = parse_xml(xml_content).unwrap();
734        assert_eq!(parsed, expected);
735    }
736
737    #[test]
738    fn test_parse_csv_with_headers() {
739        let csv_content = "header1,header2\nvalueA,valueB\nvalueC,valueD";
740        let expected = json!([
741            {"header1": "valueA", "header2": "valueB"},
742            {"header1": "valueC", "header2": "valueD"}
743        ]);
744        let parsed = parse_csv(csv_content).unwrap();
745        assert_eq!(parsed, expected);
746    }
747
748    #[test]
749    fn test_parse_csv_no_headers() {
750        let csv_content = "valueA,valueB\nvalueC,valueD";
751        let expected = json!([
752            ["valueA", "valueB"],
753            ["valueC", "valueD"]
754        ]);
755        let parsed = parse_csv(csv_content).unwrap();
756        assert_eq!(parsed, expected);
757    }
758}