1use crate::json_path::JsonPath;
2use crate::json_path::eval::{JsonPathMatchState, JsonPathMatcher};
3use mime::Mime;
4use semdiff_core::fs::FileLeaf;
5use semdiff_core::{Diff, DiffCalculator, MayUnsupported};
6use serde_json::Value;
7use similar::algorithms::DiffHook;
8use std::cmp::Reverse;
9use std::collections::BinaryHeap;
10use std::fmt::Display;
11use std::{convert, fmt, mem};
12
13pub mod json_path;
14pub mod report_html;
15pub mod report_json;
16pub mod report_summary;
17
18#[cfg(test)]
19mod tests;
20
21#[derive(Debug, Clone, Copy, Default)]
22pub struct JsonDiffReporter;
23
24#[derive(Debug)]
25enum JsonDiffBody {
26 Equal { body: String, ignored_lines: JsonDiffLines },
27 Modified(JsonDiffLines),
28}
29
30#[derive(Debug)]
31pub struct JsonDiff {
32 body: JsonDiffBody,
33}
34
35impl Diff for JsonDiff {
36 fn equal(&self) -> bool {
37 matches!(self.body, JsonDiffBody::Equal { .. })
38 }
39}
40
41impl JsonDiff {
42 fn body(&self) -> &JsonDiffBody {
43 &self.body
44 }
45}
46
47#[derive(Debug, Clone)]
48pub struct JsonDiffCalculator {
49 ignore_object_key_order: bool,
50 ignore_paths: Vec<JsonPath>,
51}
52
53impl Default for JsonDiffCalculator {
54 fn default() -> Self {
55 Self::new(false, Vec::new())
56 }
57}
58
59impl JsonDiffCalculator {
60 pub fn new(ignore_object_key_order: bool, ignore_paths: Vec<JsonPath>) -> Self {
61 Self {
62 ignore_object_key_order,
63 ignore_paths,
64 }
65 }
66
67 pub fn ignore_object_key_order(&self) -> bool {
68 self.ignore_object_key_order
69 }
70}
71
72impl DiffCalculator<FileLeaf> for JsonDiffCalculator {
73 type Error = convert::Infallible;
74 type Diff = JsonDiff;
75
76 fn diff(
77 &self,
78 _name: &str,
79 expected: FileLeaf,
80 actual: FileLeaf,
81 ) -> Result<MayUnsupported<Self::Diff>, Self::Error> {
82 if !is_json_mime(&expected.kind) || !is_json_mime(&actual.kind) {
83 return Ok(MayUnsupported::Unsupported);
84 }
85 let Ok(mut expected) = serde_json::from_slice::<Value>(&expected.content) else {
86 return Ok(MayUnsupported::Unsupported);
87 };
88 let Ok(mut actual) = serde_json::from_slice::<Value>(&actual.content) else {
89 return Ok(MayUnsupported::Unsupported);
90 };
91 if self.ignore_object_key_order {
92 expected.sort_all_objects();
93 actual.sort_all_objects();
94 }
95 let diff = json_diff(&expected, &actual, &self.ignore_paths);
96 let body = if diff.iter().all(JsonDiffLine::is_equal_for_result) {
97 let ignored_lines = if diff.iter().any(JsonDiffLine::is_ignored) {
98 diff
99 } else {
100 JsonDiffLines::default()
101 };
102 JsonDiffBody::Equal {
103 body: serde_json::to_string_pretty(&expected).unwrap(),
104 ignored_lines,
105 }
106 } else {
107 JsonDiffBody::Modified(diff)
108 };
109 let result = JsonDiff { body };
110 Ok(MayUnsupported::Ok(result))
111 }
112}
113
114fn is_json_mime(kind: &Mime) -> bool {
115 if kind == &mime::APPLICATION_JSON {
116 return true;
117 }
118 if kind.type_() == mime::APPLICATION
119 && let Some(suffix) = kind.subtype().as_str().strip_suffix("+json")
120 {
121 return !suffix.is_empty();
122 }
123 kind.essence_str() == "text/json"
124}
125
126fn try_into_json(content: &[u8]) -> Option<String> {
127 let value = serde_json::from_slice::<Value>(content).ok()?;
128 Some(serde_json::to_string_pretty(&value).unwrap())
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132enum ChangeTag {
133 Unchanged,
134 Ignored,
135 Added,
136 Deleted,
137}
138
139#[derive(Debug)]
140struct JsonDiffLine {
141 indent: usize,
142 state: JsonDiffLineState,
143}
144
145impl JsonDiffLine {
146 fn tag(&self) -> ChangeTag {
147 match self.state {
148 JsonDiffLineState::Unchanged { .. } => ChangeTag::Unchanged,
149 JsonDiffLineState::Ignored { .. } => ChangeTag::Ignored,
150 JsonDiffLineState::Added(_) => ChangeTag::Added,
151 JsonDiffLineState::Deleted(_) => ChangeTag::Deleted,
152 }
153 }
154
155 fn is_equal_for_result(&self) -> bool {
156 matches!(
157 self.state,
158 JsonDiffLineState::Unchanged { .. } | JsonDiffLineState::Ignored { .. }
159 )
160 }
161
162 fn is_ignored(&self) -> bool {
163 matches!(self.state, JsonDiffLineState::Ignored { .. })
164 }
165
166 fn has_expected(&self) -> bool {
167 !matches!(
168 self.state,
169 JsonDiffLineState::Added(_) | JsonDiffLineState::Ignored { expected: None, .. }
170 )
171 }
172
173 fn has_actual(&self) -> bool {
174 !matches!(
175 self.state,
176 JsonDiffLineState::Deleted(_) | JsonDiffLineState::Ignored { actual: None, .. }
177 )
178 }
179
180 fn preview_text(&self) -> &str {
181 match &self.state {
182 JsonDiffLineState::Unchanged { expected, .. } => expected,
183 JsonDiffLineState::Ignored { expected, actual } => {
184 expected.as_ref().or(actual.as_ref()).map_or("", String::as_str)
185 }
186 JsonDiffLineState::Added(actual) => actual,
187 JsonDiffLineState::Deleted(expected) => expected,
188 }
189 }
190
191 fn display_expected(&self) -> impl Display {
192 fmt::from_fn(|f| {
193 let expected = match &self.state {
194 JsonDiffLineState::Unchanged { expected, .. } => expected,
195 JsonDiffLineState::Ignored {
196 expected: Some(expected),
197 ..
198 } => expected,
199 JsonDiffLineState::Ignored { expected: None, .. } => return Ok(()),
200 JsonDiffLineState::Added(_) => return Ok(()),
201 JsonDiffLineState::Deleted(expected) => expected,
202 };
203 for _ in 0..self.indent {
204 f.write_str(" ")?;
205 }
206 f.write_str(expected)?;
207 Ok(())
208 })
209 }
210
211 fn display_actual(&self) -> impl Display {
212 fmt::from_fn(|f| {
213 let actual = match &self.state {
214 JsonDiffLineState::Unchanged { actual, .. } => actual,
215 JsonDiffLineState::Ignored {
216 actual: Some(actual), ..
217 } => actual,
218 JsonDiffLineState::Ignored { actual: None, .. } => return Ok(()),
219 JsonDiffLineState::Added(actual) => actual,
220 JsonDiffLineState::Deleted(_) => return Ok(()),
221 };
222 for _ in 0..self.indent {
223 f.write_str(" ")?;
224 }
225 f.write_str(actual)?;
226 Ok(())
227 })
228 }
229}
230
231#[derive(Debug)]
232enum JsonDiffLineState {
233 Unchanged {
234 expected: String,
235 actual: String,
236 },
237 Ignored {
238 expected: Option<String>,
239 actual: Option<String>,
240 },
241 Added(String),
242 Deleted(String),
243}
244
245#[derive(Debug, Default)]
246struct JsonDiffLines {
247 lines: Vec<JsonDiffLine>,
248}
249
250impl JsonDiffLines {
251 fn writer(&mut self) -> JsonDiffLineWriter<'_> {
252 JsonDiffLineWriter {
253 lines: &mut self.lines,
254 indent: 0,
255 }
256 }
257}
258
259impl std::ops::Deref for JsonDiffLines {
260 type Target = [JsonDiffLine];
261
262 fn deref(&self) -> &Self::Target {
263 &self.lines
264 }
265}
266
267impl IntoIterator for JsonDiffLines {
268 type Item = JsonDiffLine;
269 type IntoIter = std::vec::IntoIter<JsonDiffLine>;
270
271 fn into_iter(self) -> Self::IntoIter {
272 self.lines.into_iter()
273 }
274}
275
276struct JsonDiffLineWriter<'a> {
277 lines: &'a mut Vec<JsonDiffLine>,
278 indent: usize,
279}
280
281#[derive(Clone, Copy)]
282struct RenderedJson<'a> {
283 body: &'a str,
284 prefix: Option<&'a str>,
285 trailing_comma: bool,
286}
287
288impl<'a> RenderedJson<'a> {
289 fn lines(self) -> RenderedJsonLines<'a> {
290 RenderedJsonLines {
291 lines: self.body.lines().peekable(),
292 prefix: self.prefix,
293 trailing_comma: self.trailing_comma,
294 }
295 }
296}
297
298struct RenderedJsonLines<'a> {
299 lines: std::iter::Peekable<std::str::Lines<'a>>,
300 prefix: Option<&'a str>,
301 trailing_comma: bool,
302}
303
304impl<'a> Iterator for RenderedJsonLines<'a> {
305 type Item = RenderedJsonLine<'a>;
306
307 fn next(&mut self) -> Option<Self::Item> {
308 let line = self.lines.next()?;
309 Some(RenderedJsonLine {
310 prefix: self.prefix.take(),
311 line,
312 trailing_comma: self.trailing_comma && self.lines.peek().is_none(),
313 })
314 }
315}
316
317struct RenderedJsonLine<'a> {
318 prefix: Option<&'a str>,
319 line: &'a str,
320 trailing_comma: bool,
321}
322
323impl Display for RenderedJsonLine<'_> {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 if let Some(prefix) = self.prefix {
326 f.write_str(prefix)?;
327 }
328 f.write_str(self.line)?;
329 if self.trailing_comma {
330 f.write_str(",")?;
331 }
332 Ok(())
333 }
334}
335
336struct ClosingLine {
337 delimiter: char,
338 trailing_comma: bool,
339}
340
341impl Display for ClosingLine {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 write!(f, "{}", self.delimiter)?;
344 if self.trailing_comma {
345 f.write_str(",")?;
346 }
347 Ok(())
348 }
349}
350
351struct MemberContainerStart<'a> {
352 quoted_key: &'a str,
353 delimiter: char,
354}
355
356impl Display for MemberContainerStart<'_> {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 f.write_str(self.quoted_key)?;
359 f.write_str(": ")?;
360 write!(f, "{}", self.delimiter)?;
361 Ok(())
362 }
363}
364
365impl JsonDiffLineWriter<'_> {
366 fn indent(&mut self) -> JsonDiffLineWriter<'_> {
367 JsonDiffLineWriter {
368 lines: &mut *self.lines,
369 indent: self.indent + 1,
370 }
371 }
372
373 fn unchanged_display(&mut self, expected: impl Display, actual: impl Display) {
374 self.lines.push(JsonDiffLine {
375 indent: self.indent,
376 state: JsonDiffLineState::Unchanged {
377 expected: expected.to_string(),
378 actual: actual.to_string(),
379 },
380 });
381 }
382
383 fn unchanged_same(&mut self, line: &'static str) {
384 self.lines.push(JsonDiffLine {
385 indent: self.indent,
386 state: JsonDiffLineState::Unchanged {
387 expected: line.to_owned(),
388 actual: line.to_owned(),
389 },
390 });
391 }
392
393 fn unchanged_same_display(&mut self, line: impl Display) {
394 let line = line.to_string();
395 self.lines.push(JsonDiffLine {
396 indent: self.indent,
397 state: JsonDiffLineState::Unchanged {
398 expected: line.clone(),
399 actual: line,
400 },
401 });
402 }
403
404 fn unchanged_value(&mut self, value: &Value, expected_trailing_comma: bool, actual_trailing_comma: bool) {
405 let body = serde_json::to_string_pretty(value).unwrap();
406 self.unchanged_rendered_pair(
407 RenderedJson {
408 body: &body,
409 prefix: None,
410 trailing_comma: expected_trailing_comma,
411 },
412 RenderedJson {
413 body: &body,
414 prefix: None,
415 trailing_comma: actual_trailing_comma,
416 },
417 );
418 }
419
420 fn unchanged_member(
421 &mut self,
422 key: &str,
423 value: &Value,
424 expected_trailing_comma: bool,
425 actual_trailing_comma: bool,
426 ) {
427 let body = serde_json::to_string_pretty(value).unwrap();
428 let prefix = Self::member_prefix(key);
429 self.unchanged_rendered_pair(
430 RenderedJson {
431 body: &body,
432 prefix: Some(&prefix),
433 trailing_comma: expected_trailing_comma,
434 },
435 RenderedJson {
436 body: &body,
437 prefix: Some(&prefix),
438 trailing_comma: actual_trailing_comma,
439 },
440 );
441 }
442
443 fn added_value(&mut self, value: &Value, trailing_comma: bool) {
444 let body = serde_json::to_string_pretty(value).unwrap();
445 self.added_rendered(RenderedJson {
446 body: &body,
447 prefix: None,
448 trailing_comma,
449 });
450 }
451
452 fn added_member(&mut self, key: &str, value: &Value, trailing_comma: bool) {
453 let body = serde_json::to_string_pretty(value).unwrap();
454 let prefix = Self::member_prefix(key);
455 self.added_rendered(RenderedJson {
456 body: &body,
457 prefix: Some(&prefix),
458 trailing_comma,
459 });
460 }
461
462 fn deleted_value(&mut self, value: &Value, trailing_comma: bool) {
463 let body = serde_json::to_string_pretty(value).unwrap();
464 self.deleted_rendered(RenderedJson {
465 body: &body,
466 prefix: None,
467 trailing_comma,
468 });
469 }
470
471 fn deleted_member(&mut self, key: &str, value: &Value, trailing_comma: bool) {
472 let body = serde_json::to_string_pretty(value).unwrap();
473 let prefix = Self::member_prefix(key);
474 self.deleted_rendered(RenderedJson {
475 body: &body,
476 prefix: Some(&prefix),
477 trailing_comma,
478 });
479 }
480
481 fn ignored_value(&mut self, expected: Option<(&Value, bool)>, actual: Option<(&Value, bool)>) {
482 let expected =
483 expected.map(|(value, trailing_comma)| (serde_json::to_string_pretty(value).unwrap(), trailing_comma));
484 let actual =
485 actual.map(|(value, trailing_comma)| (serde_json::to_string_pretty(value).unwrap(), trailing_comma));
486 self.ignored_rendered_pair(
487 expected.as_ref().map(|(body, trailing_comma)| RenderedJson {
488 body,
489 prefix: None,
490 trailing_comma: *trailing_comma,
491 }),
492 actual.as_ref().map(|(body, trailing_comma)| RenderedJson {
493 body,
494 prefix: None,
495 trailing_comma: *trailing_comma,
496 }),
497 );
498 }
499
500 fn ignored_member(&mut self, expected: Option<(&str, &Value, bool)>, actual: Option<(&str, &Value, bool)>) {
501 let expected = expected.map(|(key, value, trailing_comma)| {
502 (
503 serde_json::to_string_pretty(value).unwrap(),
504 Self::member_prefix(key),
505 trailing_comma,
506 )
507 });
508 let actual = actual.map(|(key, value, trailing_comma)| {
509 (
510 serde_json::to_string_pretty(value).unwrap(),
511 Self::member_prefix(key),
512 trailing_comma,
513 )
514 });
515 self.ignored_rendered_pair(
516 expected.as_ref().map(|(body, prefix, trailing_comma)| RenderedJson {
517 body,
518 prefix: Some(prefix),
519 trailing_comma: *trailing_comma,
520 }),
521 actual.as_ref().map(|(body, prefix, trailing_comma)| RenderedJson {
522 body,
523 prefix: Some(prefix),
524 trailing_comma: *trailing_comma,
525 }),
526 );
527 }
528
529 fn unchanged_rendered_pair(&mut self, expected: RenderedJson<'_>, actual: RenderedJson<'_>) {
530 for (expected, actual) in expected.lines().zip(actual.lines()) {
531 self.unchanged_display(expected, actual);
532 }
533 }
534
535 fn added_rendered(&mut self, rendered: RenderedJson<'_>) {
536 for line in rendered.lines() {
537 self.lines.push(JsonDiffLine {
538 indent: self.indent,
539 state: JsonDiffLineState::Added(line.to_string()),
540 });
541 }
542 }
543
544 fn deleted_rendered(&mut self, rendered: RenderedJson<'_>) {
545 for line in rendered.lines() {
546 self.lines.push(JsonDiffLine {
547 indent: self.indent,
548 state: JsonDiffLineState::Deleted(line.to_string()),
549 });
550 }
551 }
552
553 fn ignored_rendered_pair(&mut self, expected: Option<RenderedJson<'_>>, actual: Option<RenderedJson<'_>>) {
554 let mut expected_lines = expected.map(RenderedJson::lines);
555 let mut actual_lines = actual.map(RenderedJson::lines);
556 loop {
557 let expected_line = expected_lines.as_mut().and_then(Iterator::next);
558 let actual_line = actual_lines.as_mut().and_then(Iterator::next);
559 if expected_line.is_none() && actual_line.is_none() {
560 break;
561 }
562 self.lines.push(JsonDiffLine {
563 indent: self.indent,
564 state: JsonDiffLineState::Ignored {
565 expected: expected_line.map(|line| line.to_string()),
566 actual: actual_line.map(|line| line.to_string()),
567 },
568 });
569 }
570 }
571
572 fn member_prefix(key: &str) -> String {
573 let mut key = serde_json::to_string(key).unwrap();
574 key.push_str(": ");
575 key
576 }
577}
578
579fn json_diff(expected: &Value, actual: &Value, ignore_paths: &[JsonPath]) -> JsonDiffLines {
580 fn json_array_diff<'stack, 'path, 'value>(
581 expected: &'value [Value],
582 actual: &'value [Value],
583 expected_state: &mut JsonPathMatchState<'stack, 'path, &'value Value>,
584 actual_state: &mut JsonPathMatchState<'stack, 'path, &'value Value>,
585 writer: &mut JsonDiffLineWriter<'_>,
586 ) {
587 let mut hook = ArrayDiffHook {
588 expected,
589 actual,
590 expected_state,
591 actual_state,
592 writer,
593 };
594 similar::algorithms::patience::diff(
595 &mut similar::algorithms::Replace::new(&mut hook),
596 expected,
597 0..expected.len(),
598 actual,
599 0..actual.len(),
600 )
601 .unwrap();
602
603 struct ArrayDiffHook<'hook, 'value, 'stack, 'path, 'lines> {
604 expected: &'value [Value],
605 actual: &'value [Value],
606 expected_state: &'hook mut JsonPathMatchState<'stack, 'path, &'value Value>,
607 actual_state: &'hook mut JsonPathMatchState<'stack, 'path, &'value Value>,
608 writer: &'hook mut JsonDiffLineWriter<'lines>,
609 }
610
611 impl DiffHook for ArrayDiffHook<'_, '_, '_, '_, '_> {
612 type Error = convert::Infallible;
613
614 fn equal(&mut self, old_index: usize, new_index: usize, len: usize) -> Result<(), Self::Error> {
615 for (expected_index, actual_index) in (old_index..).zip(new_index..).take(len) {
616 let need_extra_comma_expected = expected_index < self.expected.len() - 1;
617 let need_extra_comma_actual = actual_index < self.actual.len() - 1;
618 let v = &self.expected[expected_index];
619 self.writer
620 .unchanged_value(v, need_extra_comma_expected, need_extra_comma_actual);
621 }
622 Ok(())
623 }
624
625 fn delete(&mut self, old_index: usize, old_len: usize, _new_index: usize) -> Result<(), Self::Error> {
626 for i in (old_index..).take(old_len) {
627 let need_extra_comma = i < self.expected.len() - 1;
628 let v = &self.expected[i];
629 let expected_state = self.expected_state.advance_index(i).unwrap();
630 if expected_state.is_match() {
631 self.writer.ignored_value(Some((v, need_extra_comma)), None);
632 continue;
633 }
634 self.writer.deleted_value(v, need_extra_comma);
635 }
636 Ok(())
637 }
638
639 fn insert(&mut self, _old_index: usize, new_index: usize, new_len: usize) -> Result<(), Self::Error> {
640 for i in (new_index..).take(new_len) {
641 let need_extra_comma = i < self.actual.len() - 1;
642 let v = &self.actual[i];
643 let actual_state = self.actual_state.advance_index(i).unwrap();
644 if actual_state.is_match() {
645 self.writer.ignored_value(None, Some((v, need_extra_comma)));
646 continue;
647 }
648 self.writer.added_value(v, need_extra_comma);
649 }
650 Ok(())
651 }
652
653 fn replace(
654 &mut self,
655 old_index: usize,
656 old_len: usize,
657 new_index: usize,
658 new_len: usize,
659 ) -> Result<(), Self::Error> {
660 fn value_match_score(expected: &Value, actual: &Value) -> usize {
661 if expected == actual {
662 0
663 } else {
664 match (expected, actual) {
665 (Value::Array(expected), Value::Array(actual)) => {
666 10 + expected.len().abs_diff(actual.len())
667 }
668 (Value::Object(expected), Value::Object(actual)) => {
669 10 + expected.keys().filter(|k| !actual.contains_key(k.as_str())).count()
670 + actual.keys().filter(|k| !expected.contains_key(k.as_str())).count()
671 }
672 _ if mem::discriminant::<Value>(expected) == mem::discriminant::<Value>(actual) => 100,
673 _ => 1000,
674 }
675 }
676 }
677
678 let mut expected_to_actual = vec![None::<usize>; old_len];
679 let mut actual_to_expected = vec![None::<usize>; new_len];
680
681 let mut expected_ignored = Vec::with_capacity(old_len);
682 for expected_index in 0..old_len {
683 let expected_state = self.expected_state.advance_index(old_index + expected_index).unwrap();
684 expected_ignored.push(expected_state.is_match());
685 }
686 let mut actual_ignored = Vec::with_capacity(new_len);
687 for actual_index in 0..new_len {
688 let actual_state = self.actual_state.advance_index(new_index + actual_index).unwrap();
689 actual_ignored.push(actual_state.is_match());
690 }
691
692 let mut q = BinaryHeap::new();
693 for (expected_index, expected) in self.expected[old_index..][..old_len].iter().enumerate() {
694 for (actual_index, actual) in self.actual[new_index..][..new_len].iter().enumerate() {
695 q.push((
696 Reverse(value_match_score(expected, actual)),
697 expected_ignored[expected_index] == actual_ignored[actual_index],
698 Reverse(expected_index.abs_diff(actual_index)),
699 expected_index,
700 actual_index,
701 ));
702 }
703 }
704 while let Some((_, _, _, expected_index, actual_index)) = q.pop() {
705 if expected_to_actual[expected_index].is_some() || actual_to_expected[actual_index].is_some() {
706 continue;
707 }
708 let requirements = [
709 expected_to_actual[..expected_index]
710 .iter()
711 .rev()
712 .find_map(Option::as_ref)
713 .is_none_or(|&left| left < actual_index),
714 expected_to_actual[expected_index..]
715 .iter()
716 .find_map(Option::as_ref)
717 .is_none_or(|&right| actual_index < right),
718 actual_to_expected[..actual_index]
719 .iter()
720 .rev()
721 .find_map(Option::as_ref)
722 .is_none_or(|&left| left < expected_index),
723 actual_to_expected[actual_index..]
724 .iter()
725 .find_map(Option::as_ref)
726 .is_none_or(|&right| expected_index < right),
727 ];
728 if requirements.into_iter().all(convert::identity) {
729 expected_to_actual[expected_index] = Some(actual_index);
730 actual_to_expected[actual_index] = Some(expected_index);
731 }
732 }
733 let mut expected_to_actual = expected_to_actual.into_iter().enumerate().peekable();
734 let mut actual_to_expected = actual_to_expected.into_iter().enumerate().peekable();
735 let expected_index_base = old_index;
736 let actual_index_base = new_index;
737 loop {
738 while let Some((expected_index, _)) =
739 expected_to_actual.next_if(|(_, actual_index)| actual_index.is_none())
740 {
741 self.delete(expected_index_base + expected_index, 1, 0)?;
742 }
743 while let Some((actual_index, _)) =
744 actual_to_expected.next_if(|(_, expected_index)| expected_index.is_none())
745 {
746 self.insert(0, actual_index_base + actual_index, 1)?;
747 }
748 match (expected_to_actual.next(), actual_to_expected.next()) {
749 (None, None) => break,
750 (Some((expected_index, Some(actual_index_))), Some((actual_index, Some(expected_index_)))) => {
751 assert_eq!(expected_index, expected_index_);
752 assert_eq!(actual_index, actual_index_);
753 let expected_index = expected_index_base + expected_index;
754 let actual_index = actual_index_base + actual_index;
755 let need_extra_comma_expected = expected_index < self.expected.len() - 1;
756 let need_extra_comma_actual = actual_index < self.actual.len() - 1;
757 let expected_value = &self.expected[expected_index];
758 let actual_value = &self.actual[actual_index];
759 let mut expected_state = self.expected_state.advance_index(expected_index).unwrap();
760 let mut actual_state = self.actual_state.advance_index(actual_index).unwrap();
761 if expected_state.is_match() || actual_state.is_match() {
762 self.writer.ignored_value(
763 Some((expected_value, need_extra_comma_expected)),
764 Some((actual_value, need_extra_comma_actual)),
765 );
766 continue;
767 }
768 if expected_value == actual_value {
769 self.writer.unchanged_value(
770 expected_value,
771 need_extra_comma_expected,
772 need_extra_comma_actual,
773 );
774 continue;
775 }
776 match (expected_value, actual_value) {
777 (Value::Array(expected), Value::Array(actual)) => {
778 self.writer.unchanged_same("[");
779 let mut result = self.writer.indent();
780 json_array_diff(
781 expected,
782 actual,
783 &mut expected_state,
784 &mut actual_state,
785 &mut result,
786 );
787 self.writer.unchanged_display(
788 ClosingLine {
789 delimiter: ']',
790 trailing_comma: need_extra_comma_expected,
791 },
792 ClosingLine {
793 delimiter: ']',
794 trailing_comma: need_extra_comma_actual,
795 },
796 );
797 }
798 (Value::Object(expected), Value::Object(actual)) => {
799 self.writer.unchanged_same("{");
800 let mut result = self.writer.indent();
801 json_object_diff(
802 expected,
803 actual,
804 &mut expected_state,
805 &mut actual_state,
806 &mut result,
807 );
808 self.writer.unchanged_display(
809 ClosingLine {
810 delimiter: '}',
811 trailing_comma: need_extra_comma_expected,
812 },
813 ClosingLine {
814 delimiter: '}',
815 trailing_comma: need_extra_comma_actual,
816 },
817 );
818 }
819 _ => {
820 drop(expected_state);
821 drop(actual_state);
822 self.delete(expected_index, 1, 0)?;
823 self.insert(0, actual_index, 1)?;
824 }
825 }
826 }
827 _ => unreachable!(),
828 }
829 }
830 Ok(())
831 }
832 }
833 }
834 fn json_object_diff<'stack, 'path, 'value>(
835 expected: &'value serde_json::Map<String, Value>,
836 actual: &'value serde_json::Map<String, Value>,
837 expected_state: &mut JsonPathMatchState<'stack, 'path, &'value Value>,
838 actual_state: &mut JsonPathMatchState<'stack, 'path, &'value Value>,
839 writer: &mut JsonDiffLineWriter<'_>,
840 ) {
841 let expected_keys = expected.keys().collect::<Vec<_>>();
842 let actual_keys = actual.keys().collect::<Vec<_>>();
843 let mut hook = ObjectDiffHook {
844 expected,
845 actual,
846 expected_keys: &expected_keys,
847 actual_keys: &actual_keys,
848 expected_state,
849 actual_state,
850 writer,
851 };
852 similar::algorithms::patience::diff(
853 &mut hook,
854 &expected_keys,
855 0..expected_keys.len(),
856 &actual_keys,
857 0..actual_keys.len(),
858 )
859 .unwrap();
860
861 struct ObjectDiffHook<'hook, 'value, 'stack, 'path, 'lines> {
862 expected: &'value serde_json::Map<String, Value>,
863 actual: &'value serde_json::Map<String, Value>,
864 expected_keys: &'hook [&'value String],
865 actual_keys: &'hook [&'value String],
866 expected_state: &'hook mut JsonPathMatchState<'stack, 'path, &'value Value>,
867 actual_state: &'hook mut JsonPathMatchState<'stack, 'path, &'value Value>,
868 writer: &'hook mut JsonDiffLineWriter<'lines>,
869 }
870
871 impl DiffHook for ObjectDiffHook<'_, '_, '_, '_, '_> {
872 type Error = convert::Infallible;
873
874 fn equal(&mut self, old_index: usize, new_index: usize, len: usize) -> Result<(), Self::Error> {
875 assert_eq!(len, 1);
876 let need_extra_comma_expected = old_index < self.expected_keys.len() - 1;
877 let need_extra_comma_actual = new_index < self.actual_keys.len() - 1;
878 let k = self.expected_keys[old_index];
879 let expected_v = self.expected.get(k).unwrap();
880 let actual_v = self.actual.get(k).unwrap();
881 let mut expected_state = self.expected_state.advance_name(k).unwrap();
882 let mut actual_state = self.actual_state.advance_name(k).unwrap();
883 if (expected_state.is_match() || actual_state.is_match()) && expected_v != actual_v {
884 self.writer.ignored_member(
885 Some((k, expected_v, need_extra_comma_expected)),
886 Some((k, actual_v, need_extra_comma_actual)),
887 );
888 return Ok(());
889 }
890 match (expected_v, actual_v) {
891 (expected @ Value::Null, actual @ Value::Null)
892 | (expected @ Value::Bool(_), actual @ Value::Bool(_))
893 | (expected @ Value::Number(_), actual @ Value::Number(_))
894 | (expected @ Value::String(_), actual @ Value::String(_))
895 if expected == actual =>
896 {
897 self.writer
898 .unchanged_member(k, expected, need_extra_comma_expected, need_extra_comma_actual);
899 }
900 (Value::Array(expected), Value::Array(actual)) => {
901 let quoted_key = serde_json::to_string(k).unwrap();
902 self.writer.unchanged_same_display(MemberContainerStart {
903 quoted_key: "ed_key,
904 delimiter: '[',
905 });
906 let mut result = self.writer.indent();
907 json_array_diff(expected, actual, &mut expected_state, &mut actual_state, &mut result);
908 self.writer.unchanged_display(
909 ClosingLine {
910 delimiter: ']',
911 trailing_comma: need_extra_comma_expected,
912 },
913 ClosingLine {
914 delimiter: ']',
915 trailing_comma: need_extra_comma_actual,
916 },
917 );
918 }
919 (Value::Object(expected), Value::Object(actual)) => {
920 let quoted_key = serde_json::to_string(k).unwrap();
921 self.writer.unchanged_same_display(MemberContainerStart {
922 quoted_key: "ed_key,
923 delimiter: '{',
924 });
925 let mut result = self.writer.indent();
926 json_object_diff(expected, actual, &mut expected_state, &mut actual_state, &mut result);
927 self.writer.unchanged_display(
928 ClosingLine {
929 delimiter: '}',
930 trailing_comma: need_extra_comma_expected,
931 },
932 ClosingLine {
933 delimiter: '}',
934 trailing_comma: need_extra_comma_actual,
935 },
936 );
937 }
938 _ => {
939 drop(expected_state);
940 drop(actual_state);
941 self.delete(old_index, 1, 0)?;
942 self.insert(0, new_index, 1)?;
943 }
944 }
945 Ok(())
946 }
947
948 fn delete(&mut self, old_index: usize, old_len: usize, _new_index: usize) -> Result<(), Self::Error> {
949 assert_eq!(old_len, 1);
950 let need_extra_comma = old_index < self.expected.len() - 1;
951 let k = self.expected_keys[old_index];
952 let v = self.expected.get(k).unwrap();
953 let expected_state = self.expected_state.advance_name(k).unwrap();
954 if expected_state.is_match() {
955 self.writer.ignored_member(Some((k, v, need_extra_comma)), None);
956 return Ok(());
957 }
958 self.writer.deleted_member(k, v, need_extra_comma);
959 Ok(())
960 }
961
962 fn insert(&mut self, _old_index: usize, new_index: usize, new_len: usize) -> Result<(), Self::Error> {
963 assert_eq!(new_len, 1);
964 let need_extra_comma = new_index < self.actual.len() - 1;
965 let k = self.actual_keys[new_index];
966 let v = self.actual.get(k).unwrap();
967 let actual_state = self.actual_state.advance_name(k).unwrap();
968 if actual_state.is_match() {
969 self.writer.ignored_member(None, Some((k, v, need_extra_comma)));
970 return Ok(());
971 }
972 self.writer.added_member(k, v, need_extra_comma);
973 Ok(())
974 }
975
976 fn replace(
977 &mut self,
978 _old_index: usize,
979 _old_len: usize,
980 _new_index: usize,
981 _new_len: usize,
982 ) -> Result<(), Self::Error> {
983 unreachable!()
984 }
985 }
986 }
987 let mut expected_matcher = JsonPathMatcher::new(ignore_paths);
988 let mut actual_matcher = JsonPathMatcher::new(ignore_paths);
989 let mut expected_state = expected_matcher.root_state(expected);
990 let mut actual_state = actual_matcher.root_state(actual);
991 let mut result = JsonDiffLines::default();
992 let mut writer = result.writer();
993 if (expected_state.is_match() || actual_state.is_match()) && expected != actual {
994 writer.ignored_value(Some((expected, false)), Some((actual, false)));
995 } else {
996 match (expected, actual) {
997 (expected @ Value::Null, actual @ Value::Null)
998 | (expected @ Value::Bool(_), actual @ Value::Bool(_))
999 | (expected @ Value::Number(_), actual @ Value::Number(_))
1000 | (expected @ Value::String(_), actual @ Value::String(_)) => {
1001 if expected == actual {
1002 writer.unchanged_value(expected, false, false);
1003 } else {
1004 writer.deleted_value(expected, false);
1005 writer.added_value(actual, false);
1006 }
1007 }
1008 (Value::Array(expected), Value::Array(actual)) => {
1009 writer.unchanged_same("[");
1010 let mut child = writer.indent();
1011 json_array_diff(expected, actual, &mut expected_state, &mut actual_state, &mut child);
1012 writer.unchanged_same("]");
1013 }
1014 (Value::Object(expected), Value::Object(actual)) => {
1015 writer.unchanged_same("{");
1016 let mut child = writer.indent();
1017 json_object_diff(expected, actual, &mut expected_state, &mut actual_state, &mut child);
1018 writer.unchanged_same("}");
1019 }
1020 (expected, actual) => {
1021 writer.deleted_value(expected, false);
1022 writer.added_value(actual, false);
1023 }
1024 }
1025 }
1026 result
1027}