1use crate::{position::InputPosition, Input};
2use serde::Serialize;
3use std::fmt::{Display, Formatter};
4
5#[derive(Debug, Clone, Serialize)]
6pub struct InputDiff {
7 input: Input,
8 position: InputPosition,
9 maybe_old_value: Option<Input>,
10 maybe_new_value: Option<Input>,
11 action: InputDiffAction,
12}
13
14#[derive(Debug, Clone, PartialEq, Serialize)]
15pub enum InputDiffAction {
16 Added,
17 Removed,
18 Updated(Option<String>),
19}
20
21impl InputDiff {
22 pub fn input(&self) -> &Input {
23 &self.input
24 }
25
26 pub fn position(&self) -> &InputPosition {
27 &self.position
28 }
29
30 pub fn maybe_old_value(&self) -> Option<&Input> {
31 self.maybe_old_value.as_ref()
32 }
33
34 pub fn maybe_new_value(&self) -> Option<&Input> {
35 self.maybe_new_value.as_ref()
36 }
37
38 pub fn action(&self) -> &InputDiffAction {
39 &self.action
40 }
41}
42
43impl Display for InputDiff {
44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
45 let position = if self.position.is_empty() {
46 "".to_string()
47 } else {
48 format!("{} ", self.position)
49 };
50 let description = &self.action;
51 let text = match (self.maybe_old_value.as_ref(), self.maybe_new_value.as_ref()) {
52 (Some(old_value), Some(new_value)) => {
53 format!("{position}value `{old_value}` {description} to new value `{new_value}`")
54 }
55 (Some(old_value), None) => {
56 format!("{position}value `{old_value}` {description}")
57 }
58 (None, Some(new_value)) => {
59 format!("{position}value `{new_value}` {description}")
60 }
61 (None, None) => format!("{position}hasn't changed"),
62 };
63 f.write_str(text.as_str())
64 }
65}
66
67impl Display for InputDiffAction {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 f.write_str(match self {
70 Self::Added => "added",
71 Self::Removed => "removed",
72 Self::Updated(None) => "updated",
73 Self::Updated(Some(updated)) => updated,
74 })
75 }
76}
77
78pub fn diff<F>(input_1: &Input, input_2: &Input, for_each_function: &mut F)
79where
80 F: FnMut(InputDiff),
81{
82 diff_with_position(input_1, input_2, for_each_function, InputPosition::new());
83}
84
85pub fn diff_with_position<F>(
86 input_1: &Input,
87 input_2: &Input,
88 for_each_function: &mut F,
89 position: InputPosition,
90) where
91 F: FnMut(InputDiff),
92{
93 if input_1 == input_2 {
94 return;
95 }
96 if input_1.is_map() && input_2.is_map() {
97 let (old_map, new_map) = (input_1.as_map(), input_2.as_map());
98 for (key, old_value) in old_map {
99 let new_position = position.new_with_key(key);
100 if let Some(new_value) = new_map.get(key) {
101 diff_with_position(old_value, new_value, for_each_function, new_position);
102 } else {
103 let diff = InputDiff {
104 input: input_1.clone(),
105 position: new_position,
106 maybe_old_value: Some(old_value.clone()),
107 maybe_new_value: None,
108 action: InputDiffAction::Removed,
109 };
110 for_each_function(diff);
111 }
112 }
113 for (key, new_value) in new_map {
114 let new_position = position.new_with_key(key);
115 if !old_map.contains_key(key) {
116 let diff = InputDiff {
117 input: input_1.clone(),
118 position: new_position,
119 maybe_old_value: None,
120 maybe_new_value: Some(new_value.clone()),
121 action: InputDiffAction::Added,
122 };
123 for_each_function(diff);
124 }
125 }
126 } else if input_1.is_list() && input_2.is_list() {
127 let (old_list, new_list) = (input_1.as_list(), input_2.as_list());
128 let (mut added_index_list, mut removed_index_list) = (Vec::new(), Vec::new());
129 for (old_index, inner_input) in old_list.iter().enumerate() {
130 if !new_list.contains(inner_input) {
131 removed_index_list.push(old_index)
132 }
133 }
134 for (new_index, inner_input) in new_list.iter().enumerate() {
135 if !old_list.contains(inner_input) {
136 added_index_list.push(new_index)
137 }
138 }
139 for added_index in &added_index_list {
140 let added_index = *added_index;
141 let new_position = position.new_with_index(added_index);
142 if removed_index_list.contains(&added_index) {
143 diff_with_position(
144 old_list.get(added_index).unwrap(),
145 new_list.get(added_index).unwrap(),
146 for_each_function,
147 new_position,
148 );
149 } else {
150 let diff = InputDiff {
151 input: input_1.clone(),
152 position: new_position,
153 maybe_old_value: None,
154 maybe_new_value: Some(new_list.get(added_index).unwrap().clone()),
155 action: InputDiffAction::Added,
156 };
157 for_each_function(diff);
158 }
159 }
160 for removed_index in removed_index_list {
161 let new_position = position.new_with_index(removed_index);
162 if !added_index_list.contains(&removed_index) {
163 let diff = InputDiff {
164 input: input_1.clone(),
165 position: new_position,
166 maybe_old_value: Some(old_list.get(removed_index).unwrap().clone()),
167 maybe_new_value: None,
168 action: InputDiffAction::Removed,
169 };
170 for_each_function(diff);
171 }
172 }
173 } else if input_1.is_str() && input_2.is_str() {
174 let diff = InputDiff {
175 input: input_1.clone(),
176 position,
177 maybe_old_value: Some(input_1.clone()),
178 maybe_new_value: Some(input_2.clone()),
179 action: InputDiffAction::Updated(None),
180 };
181 for_each_function(diff);
182 } else if input_1.is_int() && input_2.is_int() {
183 let (old_int, new_int) = (*input_1.as_int(), *input_2.as_int());
184 let description = if old_int < new_int {
185 format!("increased by {}", new_int - old_int)
186 } else {
187 format!("decreased by {}", old_int - new_int)
188 };
189 let diff = InputDiff {
190 input: input_1.clone(),
191 position,
192 maybe_old_value: Some(input_1.clone()),
193 maybe_new_value: Some(input_2.clone()),
194 action: InputDiffAction::Updated(Some(description)),
195 };
196 for_each_function(diff);
197 } else if input_1.is_float() && input_2.is_float() {
198 let (old_float, new_float) = (*input_1.as_float(), *input_2.as_float());
199 let description = if old_float < new_float {
200 format!("increased by {}", new_float - old_float)
201 } else {
202 format!("decreased by {}", old_float - new_float)
203 };
204 let diff = InputDiff {
205 input: input_1.clone(),
206 position,
207 maybe_old_value: Some(input_1.clone()),
208 maybe_new_value: Some(input_2.clone()),
209 action: InputDiffAction::Updated(Some(description)),
210 };
211 for_each_function(diff);
212 } else if input_1.is_bool() && input_2.is_bool() {
213 let diff = InputDiff {
214 input: input_1.clone(),
215 position,
216 maybe_old_value: Some(input_1.clone()),
217 maybe_new_value: Some(input_2.clone()),
218 action: InputDiffAction::Updated(None),
219 };
220 for_each_function(diff);
221 } else {
222 let diff = InputDiff {
224 input: input_1.clone(),
225 position,
226 maybe_old_value: Some(input_1.clone()),
227 maybe_new_value: Some(input_2.clone()),
228 action: InputDiffAction::Updated(Some(format!(
229 "changed from {} type to {} type",
230 input_1.type_name(),
231 input_2.type_name()
232 ))),
233 };
234 for_each_function(diff);
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::logging::{enable_logging, info};
242 use crate::position::InputPositionType;
243 use std::collections::HashMap;
244
245 fn diff_to_list(old_input: &Input, new_input: &Input, print: bool) -> Vec<InputDiff> {
246 enable_logging();
247
248 let mut diff_list = Vec::new();
249 diff(old_input, new_input, &mut |diff| {
250 if print {
251 info(format!("\t{diff}"));
252 }
253 diff_list.push(diff)
254 });
255 diff_list
256 }
257
258 #[test]
259 fn functionality() {
260 enable_logging();
261
262 let old_input = Input::from(true);
263 let mut new_input = Input::from(true);
264 let diff_list = diff_to_list(&old_input, &new_input, false);
265 assert!(diff_list.is_empty());
266 *new_input.bool_mut() = false;
267 let diff_list = diff_to_list(&old_input, &new_input, false);
268 assert!(!diff_list.is_empty());
269
270 let old_input = Input::from(100);
271 let mut new_input = Input::from(100);
272 let diff_list = diff_to_list(&old_input, &new_input, false);
273 assert!(diff_list.is_empty());
274 *new_input.int_mut() = -100;
275 let diff_list = diff_to_list(&old_input, &new_input, false);
276 assert!(!diff_list.is_empty());
277 assert_eq!(
278 diff_list[0].action.to_string(),
279 "decreased by 200".to_string()
280 );
281
282 let old_input = Input::from(-1.5);
283 let mut new_input = Input::from(-1.5);
284 let diff_list = diff_to_list(&old_input, &new_input, false);
285 assert!(diff_list.is_empty());
286 *new_input.float_mut() = 3.0;
287 let diff_list = diff_to_list(&old_input, &new_input, false);
288 assert!(!diff_list.is_empty());
289 assert_eq!(
290 diff_list[0].action.to_string(),
291 "increased by 4.5".to_string()
292 );
293
294 let old_input = Input::from("foo");
295 let mut new_input = Input::from("foo");
296 let diff_list = diff_to_list(&old_input, &new_input, false);
297 assert!(diff_list.is_empty());
298 *new_input.str_mut() = "bar".to_string();
299 let diff_list = diff_to_list(&old_input, &new_input, false);
300 assert!(!diff_list.is_empty());
301 assert_eq!(diff_list[0].action, InputDiffAction::Updated(None));
302
303 let old_input = Input::from(Vec::from([
304 Input::from(true),
305 Input::from(10),
306 Input::from(0.5),
307 Input::from("foo"),
308 ]));
309 let mut new_input = Input::from(Vec::from([
310 Input::from(true),
311 Input::from(10),
312 Input::from(0.5),
313 Input::from("foo"),
314 ]));
315 let diff_list = diff_to_list(&old_input, &new_input, false);
316 assert!(diff_list.is_empty());
317 *new_input.list_mut()[0].bool_mut() = false;
318 let diff_list = diff_to_list(&old_input, &new_input, false);
319 assert_eq!(diff_list.len(), 1);
320 assert_eq!(
321 diff_list[0].position.clone()[0],
322 InputPositionType::Index(0)
323 );
324 *new_input.list_mut()[1].int_mut() = 20;
325 let diff_list = diff_to_list(&old_input, &new_input, false);
326 assert_eq!(diff_list.len(), 2);
327 assert_eq!(
328 diff_list[1].position.clone()[0],
329 InputPositionType::Index(1)
330 );
331 *new_input.list_mut()[2].float_mut() = -0.5;
332 let diff_list = diff_to_list(&old_input, &new_input, false);
333 assert_eq!(diff_list.len(), 3);
334 assert_eq!(
335 diff_list[2].position.clone()[0],
336 InputPositionType::Index(2)
337 );
338 *new_input.list_mut()[3].str_mut() = "bar".to_string();
339 let diff_list = diff_to_list(&old_input, &new_input, false);
340 assert_eq!(diff_list.len(), 4);
341 assert_eq!(
342 diff_list[3].position.clone()[0],
343 InputPositionType::Index(3)
344 );
345
346 let old_input = Input::from(HashMap::from([(
347 "foo".to_string(),
348 Input::from(Vec::from([
349 Input::from(true),
350 Input::from(10),
351 Input::from(0.5),
352 Input::from("foo"),
353 ])),
354 )]));
355 let mut new_input = Input::from(HashMap::from([(
356 "foo".to_string(),
357 Input::from(Vec::from([
358 Input::from(true),
359 Input::from(10),
360 Input::from(0.5),
361 Input::from("foo"),
362 ])),
363 )]));
364 let diff_list = diff_to_list(&old_input, &new_input, false);
365 assert!(diff_list.is_empty());
366 *new_input.map_mut().get_mut("foo").unwrap().list_mut()[1].int_mut() = 100;
367 let diff_list = diff_to_list(&old_input, &new_input, false);
368 assert!(!diff_list.is_empty());
369 assert_eq!(
370 diff_list[0].position.clone()[0],
371 InputPositionType::Key("foo".to_string())
372 );
373 assert_eq!(
374 diff_list[0].position.clone()[1],
375 InputPositionType::Index(1)
376 );
377 }
378
379 #[test]
380 fn print() {
381 enable_logging();
382
383 let json = serde_json::json!({"foo": {"bar": {"baz": [-10, 1.5, false, "bar"]}}});
384 let input: Input = serde_json::from_value(json.clone()).unwrap();
385 let mut new_input = input.clone();
386 let list = new_input
387 .map_mut()
388 .get_mut("foo")
389 .unwrap()
390 .map_mut()
391 .get_mut("bar")
392 .unwrap()
393 .map_mut()
394 .get_mut("baz")
395 .unwrap()
396 .list_mut();
397 *list[0].int_mut() = 10;
398 *list[1].float_mut() = -1.5;
399 list[2] = Input::from("new string");
400 list.remove(3);
401 info(format!("Original data: {}", json.to_string()));
402 info(format!(
403 "Updated data: {}",
404 serde_json::to_string(&new_input).unwrap()
405 ));
406 info(format!("Updates:"));
407 diff_to_list(&input, &new_input, true);
408 }
409}