saluki_config/dynamic/
diff.rs1use super::event::ConfigChangeEvent;
4
5pub fn diff_config(old_config: &figment::value::Value, new_config: &figment::value::Value) -> Vec<ConfigChangeEvent> {
7 let mut changes = Vec::new();
8 diff_recursive(old_config, new_config, "", &mut changes);
9 changes
10}
11
12fn diff_recursive(
13 old_config: &figment::value::Value, new_config: &figment::value::Value, path: &str,
14 changes: &mut Vec<ConfigChangeEvent>,
15) {
16 if let (Some(old_dict), Some(new_dict)) = (old_config.as_dict(), new_config.as_dict()) {
17 for (key, new_value) in new_dict {
18 let current_path = if path.is_empty() {
19 key.clone()
20 } else {
21 format!("{}.{}", path, key)
22 };
23
24 match old_dict.get(key) {
25 Some(old_value) => {
26 if old_value != new_value {
27 if new_value.as_dict().is_some() && old_value.as_dict().is_some() {
28 diff_recursive(old_value, new_value, ¤t_path, changes);
29 } else {
30 changes.push(ConfigChangeEvent {
31 key: current_path,
32 old_value: Some(serde_json::to_value(old_value).unwrap()),
33 new_value: Some(serde_json::to_value(new_value).unwrap()),
34 });
35 }
36 }
37 }
38 None => {
39 changes.push(ConfigChangeEvent {
40 key: current_path,
41 old_value: None,
42 new_value: Some(serde_json::to_value(new_value).unwrap()),
43 });
44 }
45 }
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use figment::{providers::Serialized, value::Value, Figment};
53 use serde_json::json;
54
55 use super::*;
56
57 fn to_figment_value(json: serde_json::Value) -> Value {
58 let serialized = Serialized::defaults(json);
59 let value: Value = Figment::from(serialized).extract().unwrap();
60 value
61 }
62
63 #[test]
64 fn test_diff_config_basic() {
65 let old_json = json!({
66 "a": "original",
67 "nested": {
68 "b": 100
69 },
70 "unchanged": true
71 });
72
73 let new_json = json!({
74 "a": "updated", "nested": {
76 "b": 200, "c": "new" },
79 "unchanged": true,
80 "d": "added" });
82
83 let old_config = to_figment_value(old_json);
84 let new_config = to_figment_value(new_json);
85
86 let changes = diff_config(&old_config, &new_config);
87
88 assert_eq!(changes.len(), 4);
90
91 println!("changes: {:?}", changes);
92
93 assert!(changes.contains(&ConfigChangeEvent {
94 key: "a".to_string(),
95 old_value: Some("original".into()),
96 new_value: Some("updated".into())
97 }));
98 assert!(changes.contains(&ConfigChangeEvent {
99 key: "nested.b".to_string(),
100 old_value: Some(100.into()),
101 new_value: Some(200.into())
102 }));
103 assert!(changes.contains(&ConfigChangeEvent {
104 key: "nested.c".to_string(),
105 old_value: None,
106 new_value: Some("new".into())
107 }));
108 assert!(changes.contains(&ConfigChangeEvent {
109 key: "d".to_string(),
110 old_value: None,
111 new_value: Some("added".into())
112 }));
113 }
114
115 #[test]
116 fn test_diff_config_no_change() {
117 let old_json = json!({
118 "a": "original",
119 "nested": {
120 "b": 100
121 },
122 });
123
124 let new_json = old_json.clone();
125
126 let old_config = to_figment_value(old_json);
127 let new_config = to_figment_value(new_json);
128
129 let changes = diff_config(&old_config, &new_config);
130
131 assert!(changes.is_empty());
132 }
133}