From 6c59eecd613e445a7baefb10d994f12f750bfe8d Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 01:35:34 +0900 Subject: [PATCH 1/9] fill subschemas on length mismatch --- src/diff_walker.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 294f431..8b2e749 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -27,8 +27,14 @@ impl DiffWalker { if let (Some(lhs_any_of), Some(rhs_any_of)) = (&mut lhs.subschemas().any_of, &mut rhs.subschemas().any_of) { - lhs_any_of.sort_by_cached_key(|x| format!("{x:?}")); - rhs_any_of.sort_by_cached_key(|x| format!("{x:?}")); + match (lhs_any_of.len(), rhs_any_of.len()) { + (l, r) if l <= r => { + lhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); r - l]); + } + (l, r) => { + rhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); l - r]); + } + } for (i, (lhs_inner, rhs_inner)) in lhs_any_of.iter_mut().zip(rhs_any_of.iter_mut()).enumerate() From 47ed7cd574cf11945890a00a598f7c24ec1373ff Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 03:06:00 +0900 Subject: [PATCH 2/9] compare anyOf based on handmade diff score --- Cargo.toml | 1 + src/diff_walker.rs | 102 ++++++++++++++++-- tests/fixtures/any_of/objects_1.json | 18 ++++ tests/fixtures/any_of/objects_2.json | 19 ++++ tests/fixtures/any_of/objects_3.json | 19 ++++ ..._from_fixtures@any_of__objects_1.json.snap | 23 ++++ ..._from_fixtures@any_of__objects_2.json.snap | 31 ++++++ ..._from_fixtures@any_of__objects_3.json.snap | 31 ++++++ 8 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/any_of/objects_1.json create mode 100644 tests/fixtures/any_of/objects_2.json create mode 100644 tests/fixtures/any_of/objects_3.json create mode 100644 tests/snapshots/test__from_fixtures@any_of__objects_1.json.snap create mode 100644 tests/snapshots/test__from_fixtures@any_of__objects_2.json.snap create mode 100644 tests/snapshots/test__from_fixtures@any_of__objects_3.json.snap diff --git a/Cargo.toml b/Cargo.toml index 4e6066d..c7215cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ schemars = { version = "0.8.12", default_features = false } serde = "1.0.158" serde_json = "1.0.94" thiserror = "1.0.40" +hungarian = "1.1.1" [features] build-binary = ["clap", "anyhow"] diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 8b2e749..3ff3e10 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use schemars::schema::{ InstanceType, NumberValidation, ObjectValidation, RootSchema, Schema, SchemaObject, - SingleOrVec, SubschemaValidation, + SingleOrVec, StringValidation, SubschemaValidation, }; use serde_json::Value; @@ -14,6 +14,81 @@ pub struct DiffWalker { pub rhs_root: RootSchema, } +trait DiffScore { + fn diff_score(&mut self, rhs: &mut Self) -> usize; +} + +impl DiffScore for Schema { + fn diff_score(&mut self, rhs: &mut Self) -> usize { + self.clone() + .into_object() + .diff_score(&mut rhs.clone().into_object()) + } +} +impl DiffScore for SchemaObject { + fn diff_score(&mut self, rhs: &mut Self) -> usize { + let mut score = 0; + if self.effective_type() != rhs.effective_type() { + score += 10; + } + score += self.number().diff_score(rhs.number()) + + self.string().diff_score(rhs.string()) + + self.object().diff_score(rhs.object()); + score + } +} + +impl DiffScore for NumberValidation { + fn diff_score(&mut self, rhs: &mut Self) -> usize { + let mut score = 0; + if self.multiple_of != rhs.multiple_of { + score += 1; + } + if self.minimum != rhs.minimum { + score += 1; + } + if self.maximum != rhs.maximum { + score += 1; + } + score + } +} + +impl DiffScore for StringValidation { + fn diff_score(&mut self, rhs: &mut Self) -> usize { + let mut score = 0; + if self.pattern != rhs.pattern { + score += 1; + } + if self.min_length != rhs.min_length { + score += 1; + } + if self.max_length != rhs.max_length { + score += 1; + } + score + } +} + +impl DiffScore for ObjectValidation { + fn diff_score(&mut self, rhs: &mut Self) -> usize { + let mut score = 0; + if self.required != rhs.required { + score += 1; + } + if self.properties != rhs.properties { + score += 1; + } + if self.pattern_properties != rhs.pattern_properties { + score += 1; + } + if self.additional_properties != rhs.additional_properties { + score += 1; + } + score + } +} + impl DiffWalker { fn diff_any_of( &mut self, @@ -29,16 +104,27 @@ impl DiffWalker { { match (lhs_any_of.len(), rhs_any_of.len()) { (l, r) if l <= r => { - lhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); r - l]); + lhs_any_of.append(&mut vec![Schema::Bool(false); r - l]); } (l, r) => { - rhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); l - r]); + rhs_any_of.append(&mut vec![Schema::Bool(false); l - r]); } } - for (i, (lhs_inner, rhs_inner)) in - lhs_any_of.iter_mut().zip(rhs_any_of.iter_mut()).enumerate() - { + let mut mat = vec![]; + let len = lhs_any_of.len(); + lhs_any_of.iter_mut().for_each(|l| { + rhs_any_of + .iter_mut() + .for_each(|r| mat.push(l.diff_score(r))) + }); + let pairs = hungarian::minimize(&mat, len, len) + .into_iter() + .enumerate() + .filter_map(|(i, j)| j.map(|j| (i, j))) + .collect::>(); + + for i in 0..len { let new_path = match is_rhs_split { true => json_path.to_owned(), false => format!("{json_path}."), @@ -46,8 +132,8 @@ impl DiffWalker { self.do_diff( &new_path, true, - &mut lhs_inner.clone().into_object(), - &mut rhs_inner.clone().into_object(), + &mut lhs_any_of[pairs[i].0].clone().into_object(), + &mut rhs_any_of[pairs[i].1].clone().into_object(), )?; } } diff --git a/tests/fixtures/any_of/objects_1.json b/tests/fixtures/any_of/objects_1.json new file mode 100644 index 0000000..8a97970 --- /dev/null +++ b/tests/fixtures/any_of/objects_1.json @@ -0,0 +1,18 @@ +{ + "lhs": { + "anyOf": [ + {"properties": {"foo": {}}}, + {"properties": {"type": {"const": "bar"}}} + ] + }, + "rhs": { + "anyOf": [ + { + "title": "replay_recording", + "type": "object", + "properties": {"foo": {}} + }, + {"properties": {"type": {"const": "bar"}}} + ] + } +} diff --git a/tests/fixtures/any_of/objects_2.json b/tests/fixtures/any_of/objects_2.json new file mode 100644 index 0000000..e9bcf89 --- /dev/null +++ b/tests/fixtures/any_of/objects_2.json @@ -0,0 +1,19 @@ +{ + "lhs": { + "anyOf": [ + {"properties": {"foo": {}}}, + {"properties": {"type": {"const": "bar"}}} + ] + }, + "rhs": { + "anyOf": [ + { "type": "boolean" }, + { + "title": "replay_recording", + "type": "object", + "properties": {"foo": {}} + }, + {"properties": {"type": {"const": "bar"}}} + ] + } +} diff --git a/tests/fixtures/any_of/objects_3.json b/tests/fixtures/any_of/objects_3.json new file mode 100644 index 0000000..3d30029 --- /dev/null +++ b/tests/fixtures/any_of/objects_3.json @@ -0,0 +1,19 @@ +{ + "lhs": { + "anyOf": [ + { "type": "boolean" }, + {"properties": {"foo": {}}}, + {"properties": {"type": {"const": "bar"}}} + ] + }, + "rhs": { + "anyOf": [ + { + "title": "replay_recording", + "type": "object", + "properties": {"foo": {}} + }, + {"properties": {"type": {"const": "bar"}}} + ] + } +} diff --git a/tests/snapshots/test__from_fixtures@any_of__objects_1.json.snap b/tests/snapshots/test__from_fixtures@any_of__objects_1.json.snap new file mode 100644 index 0000000..052d3ea --- /dev/null +++ b/tests/snapshots/test__from_fixtures@any_of__objects_1.json.snap @@ -0,0 +1,23 @@ +--- +source: tests/test.rs +expression: diff +info: + lhs: + anyOf: + - properties: + foo: {} + - properties: + type: + const: bar + rhs: + anyOf: + - properties: + foo: {} + title: replay_recording + type: object + - properties: + type: + const: bar +input_file: tests/fixtures/any_of/objects_1.json +--- +[] diff --git a/tests/snapshots/test__from_fixtures@any_of__objects_2.json.snap b/tests/snapshots/test__from_fixtures@any_of__objects_2.json.snap new file mode 100644 index 0000000..ce6bfc6 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@any_of__objects_2.json.snap @@ -0,0 +1,31 @@ +--- +source: tests/test.rs +expression: diff +info: + lhs: + anyOf: + - properties: + foo: {} + - properties: + type: + const: bar + rhs: + anyOf: + - type: boolean + - properties: + foo: {} + title: replay_recording + type: object + - properties: + type: + const: bar +input_file: tests/fixtures/any_of/objects_2.json +--- +[ + Change { + path: "", + change: TypeAdd { + added: Boolean, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@any_of__objects_3.json.snap b/tests/snapshots/test__from_fixtures@any_of__objects_3.json.snap new file mode 100644 index 0000000..9749788 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@any_of__objects_3.json.snap @@ -0,0 +1,31 @@ +--- +source: tests/test.rs +expression: diff +info: + lhs: + anyOf: + - type: boolean + - properties: + foo: {} + - properties: + type: + const: bar + rhs: + anyOf: + - properties: + foo: {} + title: replay_recording + type: object + - properties: + type: + const: bar +input_file: tests/fixtures/any_of/objects_3.json +--- +[ + Change { + path: "", + change: TypeRemove { + removed: Boolean, + }, + }, +] From e3d2f8e1e2a1874d0309fa27bf67d000266909fc Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 03:38:14 +0900 Subject: [PATCH 3/9] add test on multiple number schemas within anyOf --- src/diff_walker.rs | 3 +- tests/fixtures/any_of/number_1.json | 16 +++++ ...__from_fixtures@any_of__number_1.json.snap | 58 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/any_of/number_1.json create mode 100644 tests/snapshots/test__from_fixtures@any_of__number_1.json.snap diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 3ff3e10..1163b9f 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -127,7 +127,7 @@ impl DiffWalker { for i in 0..len { let new_path = match is_rhs_split { true => json_path.to_owned(), - false => format!("{json_path}."), + false => format!("{json_path}.", pairs[i].1), }; self.do_diff( &new_path, @@ -624,6 +624,7 @@ impl JsonSchemaExt for SchemaObject { self.subschemas() .any_of .as_ref() + .filter(|schemas| schemas.len() == 1) .and_then(|a| a.get(0)) .map(|subschema| subschema.clone().into_object().number().clone()) .unwrap_or_default() diff --git a/tests/fixtures/any_of/number_1.json b/tests/fixtures/any_of/number_1.json new file mode 100644 index 0000000..ab0486a --- /dev/null +++ b/tests/fixtures/any_of/number_1.json @@ -0,0 +1,16 @@ +{ + "lhs": { + "anyOf": [ + {"type": "number", "maximum": 10}, + {"type": "number", "minimum": 1}, + {"type": "number", "minimum": 100, "maximum": 200} + ] + }, + "rhs": { + "anyOf": [ + {"type": "number", "minimum": 7, "maximum": 14}, + {"type": "number", "maximum": 3}, + {"type": "number", "minimum": 2} + ] + } +} diff --git a/tests/snapshots/test__from_fixtures@any_of__number_1.json.snap b/tests/snapshots/test__from_fixtures@any_of__number_1.json.snap new file mode 100644 index 0000000..5b7e939 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@any_of__number_1.json.snap @@ -0,0 +1,58 @@ +--- +source: tests/test.rs +expression: diff +info: + lhs: + anyOf: + - maximum: 10 + type: number + - minimum: 1 + type: number + - maximum: 200 + minimum: 100 + type: number + rhs: + anyOf: + - maximum: 14 + minimum: 7 + type: number + - maximum: 3 + type: number + - minimum: 2 + type: number +input_file: tests/fixtures/any_of/number_1.json +--- +[ + Change { + path: ".", + change: RangeChange { + changed: Maximum, + old_value: 10.0, + new_value: 3.0, + }, + }, + Change { + path: ".", + change: RangeChange { + changed: Minimum, + old_value: 1.0, + new_value: 2.0, + }, + }, + Change { + path: ".", + change: RangeChange { + changed: Minimum, + old_value: 100.0, + new_value: 7.0, + }, + }, + Change { + path: ".", + change: RangeChange { + changed: Maximum, + old_value: 200.0, + new_value: 14.0, + }, + }, +] From b0ff30c0cb7b5305a5ce6b03ec19c25114c83132 Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 13:10:56 +0900 Subject: [PATCH 4/9] use `diff` to compute the score --- src/diff_walker.rs | 95 ++++++++-------------------------------------- 1 file changed, 15 insertions(+), 80 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 1163b9f..7e6cca0 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -14,81 +14,6 @@ pub struct DiffWalker { pub rhs_root: RootSchema, } -trait DiffScore { - fn diff_score(&mut self, rhs: &mut Self) -> usize; -} - -impl DiffScore for Schema { - fn diff_score(&mut self, rhs: &mut Self) -> usize { - self.clone() - .into_object() - .diff_score(&mut rhs.clone().into_object()) - } -} -impl DiffScore for SchemaObject { - fn diff_score(&mut self, rhs: &mut Self) -> usize { - let mut score = 0; - if self.effective_type() != rhs.effective_type() { - score += 10; - } - score += self.number().diff_score(rhs.number()) - + self.string().diff_score(rhs.string()) - + self.object().diff_score(rhs.object()); - score - } -} - -impl DiffScore for NumberValidation { - fn diff_score(&mut self, rhs: &mut Self) -> usize { - let mut score = 0; - if self.multiple_of != rhs.multiple_of { - score += 1; - } - if self.minimum != rhs.minimum { - score += 1; - } - if self.maximum != rhs.maximum { - score += 1; - } - score - } -} - -impl DiffScore for StringValidation { - fn diff_score(&mut self, rhs: &mut Self) -> usize { - let mut score = 0; - if self.pattern != rhs.pattern { - score += 1; - } - if self.min_length != rhs.min_length { - score += 1; - } - if self.max_length != rhs.max_length { - score += 1; - } - score - } -} - -impl DiffScore for ObjectValidation { - fn diff_score(&mut self, rhs: &mut Self) -> usize { - let mut score = 0; - if self.required != rhs.required { - score += 1; - } - if self.properties != rhs.properties { - score += 1; - } - if self.pattern_properties != rhs.pattern_properties { - score += 1; - } - if self.additional_properties != rhs.additional_properties { - score += 1; - } - score - } -} - impl DiffWalker { fn diff_any_of( &mut self, @@ -113,11 +38,21 @@ impl DiffWalker { let mut mat = vec![]; let len = lhs_any_of.len(); - lhs_any_of.iter_mut().for_each(|l| { - rhs_any_of - .iter_mut() - .for_each(|r| mat.push(l.diff_score(r))) - }); + for l in lhs_any_of.iter_mut() { + for r in rhs_any_of.iter_mut() { + let mut walker = DiffWalker { + changes: vec![], + lhs_root: self.lhs_root.clone(), + rhs_root: self.rhs_root.clone(), + }; + walker.diff( + "", + &mut l.clone().into_object(), + &mut r.clone().into_object(), + )?; + mat.push(walker.changes.len()); + } + } let pairs = hungarian::minimize(&mat, len, len) .into_iter() .enumerate() From deb50d944c3ac7ad1432a9f3a2106e00bf8def14 Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 13:27:06 +0900 Subject: [PATCH 5/9] `hungarian` is dead; use `pathfinding` instead --- Cargo.toml | 2 +- src/diff_walker.rs | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c7215cb..94e8b05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ schemars = { version = "0.8.12", default_features = false } serde = "1.0.158" serde_json = "1.0.94" thiserror = "1.0.40" -hungarian = "1.1.1" +pathfinding = "4.2.1" [features] build-binary = ["clap", "anyhow"] diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 7e6cca0..3b9040f 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use schemars::schema::{ InstanceType, NumberValidation, ObjectValidation, RootSchema, Schema, SchemaObject, - SingleOrVec, StringValidation, SubschemaValidation, + SingleOrVec, SubschemaValidation, }; use serde_json::Value; @@ -36,10 +36,10 @@ impl DiffWalker { } } - let mut mat = vec![]; let len = lhs_any_of.len(); - for l in lhs_any_of.iter_mut() { - for r in rhs_any_of.iter_mut() { + let mut mat = pathfinding::matrix::Matrix::new(len, len, 0i32); + for (i, l) in lhs_any_of.iter_mut().enumerate() { + for (j, r) in rhs_any_of.iter_mut().enumerate() { let mut walker = DiffWalker { changes: vec![], lhs_root: self.lhs_root.clone(), @@ -50,25 +50,20 @@ impl DiffWalker { &mut l.clone().into_object(), &mut r.clone().into_object(), )?; - mat.push(walker.changes.len()); + mat[(i, j)] = i32::try_from(walker.changes.len()).expect("too many changes"); } } - let pairs = hungarian::minimize(&mat, len, len) - .into_iter() - .enumerate() - .filter_map(|(i, j)| j.map(|j| (i, j))) - .collect::>(); - + let pairs = pathfinding::kuhn_munkres::kuhn_munkres_min(&mat).1; for i in 0..len { let new_path = match is_rhs_split { true => json_path.to_owned(), - false => format!("{json_path}.", pairs[i].1), + false => format!("{json_path}.", pairs[i]), }; self.do_diff( &new_path, true, - &mut lhs_any_of[pairs[i].0].clone().into_object(), - &mut rhs_any_of[pairs[i].1].clone().into_object(), + &mut lhs_any_of[i].clone().into_object(), + &mut rhs_any_of[pairs[i]].clone().into_object(), )?; } } From 0ebbfe364db08fa3306cda1b48a4c62449735a1b Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 22:47:42 +0900 Subject: [PATCH 6/9] make use of `Vec::resize` --- src/diff_walker.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 3b9040f..4aa582f 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -35,9 +35,11 @@ impl DiffWalker { rhs_any_of.append(&mut vec![Schema::Bool(false); l - r]); } } + let max_len = lhs_any_of.len().max(rhs_any_of.len()); + lhs_any_of.resize(max_len, Schema::Bool(false)); + rhs_any_of.resize(max_len, Schema::Bool(false)); - let len = lhs_any_of.len(); - let mut mat = pathfinding::matrix::Matrix::new(len, len, 0i32); + let mut mat = pathfinding::matrix::Matrix::new(max_len, max_len, 0i32); for (i, l) in lhs_any_of.iter_mut().enumerate() { for (j, r) in rhs_any_of.iter_mut().enumerate() { let mut walker = DiffWalker { @@ -54,7 +56,7 @@ impl DiffWalker { } } let pairs = pathfinding::kuhn_munkres::kuhn_munkres_min(&mat).1; - for i in 0..len { + for i in 0..max_len { let new_path = match is_rhs_split { true => json_path.to_owned(), false => format!("{json_path}.", pairs[i]), From 7b4cc56c8890f203e039a564ee002a7a0024490e Mon Sep 17 00:00:00 2001 From: 6293 Date: Wed, 17 May 2023 23:29:08 +0900 Subject: [PATCH 7/9] make `Diffwalker` take a callback --- src/diff_walker.rs | 64 ++++++++++++++++++++++++++++------------------ src/lib.rs | 12 ++++----- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 4aa582f..5f871ae 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -8,13 +8,25 @@ use serde_json::Value; use crate::{Change, ChangeKind, Error, JsonSchemaType, Range}; -pub struct DiffWalker { - pub changes: Vec, +pub struct DiffWalker<'cb> { + pub cb: Box, pub lhs_root: RootSchema, pub rhs_root: RootSchema, } -impl DiffWalker { +impl<'cb> DiffWalker<'cb> { + pub fn new( + cb: Box, + lhs_root: RootSchema, + rhs_root: RootSchema, + ) -> Self { + Self { + cb, + lhs_root, + rhs_root, + } + } + fn diff_any_of( &mut self, json_path: &str, @@ -42,17 +54,19 @@ impl DiffWalker { let mut mat = pathfinding::matrix::Matrix::new(max_len, max_len, 0i32); for (i, l) in lhs_any_of.iter_mut().enumerate() { for (j, r) in rhs_any_of.iter_mut().enumerate() { - let mut walker = DiffWalker { - changes: vec![], - lhs_root: self.lhs_root.clone(), - rhs_root: self.rhs_root.clone(), - }; - walker.diff( + let mut count = 0; + let counter = |_change: Change| count += 1; + DiffWalker::new( + Box::new(counter), + self.lhs_root.clone(), + self.rhs_root.clone(), + ) + .diff( "", &mut l.clone().into_object(), &mut r.clone().into_object(), )?; - mat[(i, j)] = i32::try_from(walker.changes.len()).expect("too many changes"); + mat[(i, j)] = count; } } let pairs = pathfinding::kuhn_munkres::kuhn_munkres_min(&mat).1; @@ -83,7 +97,7 @@ impl DiffWalker { let rhs_ty = rhs.effective_type().into_set(); for removed in lhs_ty.difference(&rhs_ty) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::TypeRemove { removed: removed.clone(), @@ -92,7 +106,7 @@ impl DiffWalker { } for added in rhs_ty.difference(&lhs_ty) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::TypeAdd { added: added.clone(), @@ -105,13 +119,13 @@ impl DiffWalker { Self::normalize_const(lhs); Self::normalize_const(rhs); match (&lhs.const_value, &rhs.const_value) { - (Some(value), None) => self.changes.push(Change { + (Some(value), None) => (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::ConstRemove { removed: value.clone(), }, }), - (None, Some(value)) => self.changes.push(Change { + (None, Some(value)) => (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::ConstAdd { added: value.clone(), @@ -119,11 +133,11 @@ impl DiffWalker { }), (Some(l), Some(r)) if l != r => { if l.is_object() && r.is_object() {} - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::ConstRemove { removed: l.clone() }, }); - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::ConstAdd { added: r.clone() }, }); @@ -148,7 +162,7 @@ impl DiffWalker { .map_or(true, |x| x.clone().into_object().is_true()); for removed in lhs_props.difference(&rhs_props) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::PropertyRemove { lhs_additional_properties, @@ -158,7 +172,7 @@ impl DiffWalker { } for added in rhs_props.difference(&lhs_props) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::PropertyAdd { lhs_additional_properties, @@ -242,14 +256,14 @@ impl DiffWalker { rhs.number_validation().minimum, Range::Minimum, ) { - self.changes.push(diff) + (self.cb)(diff) } if let Some(diff) = diff( lhs.number_validation().maximum, rhs.number_validation().maximum, Range::Maximum, ) { - self.changes.push(diff) + (self.cb)(diff) } Ok(()) } @@ -263,7 +277,7 @@ impl DiffWalker { match (&lhs.array().items, &rhs.array().items) { (Some(SingleOrVec::Vec(lhs_items)), Some(SingleOrVec::Vec(rhs_items))) => { if lhs_items.len() != rhs_items.len() { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::TupleChange { new_length: rhs_items.len(), @@ -291,7 +305,7 @@ impl DiffWalker { )?; } (Some(SingleOrVec::Single(lhs_inner)), Some(SingleOrVec::Vec(rhs_items))) => { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::ArrayToTuple { new_length: rhs_items.len(), @@ -308,7 +322,7 @@ impl DiffWalker { } } (Some(SingleOrVec::Vec(lhs_items)), Some(SingleOrVec::Single(rhs_inner))) => { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::TupleToArray { old_length: lhs_items.len(), @@ -345,7 +359,7 @@ impl DiffWalker { let rhs_required = &rhs.object().required; for removed in lhs_required.difference(rhs_required) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::RequiredRemove { property: removed.clone(), @@ -354,7 +368,7 @@ impl DiffWalker { } for added in rhs_required.difference(lhs_required) { - self.changes.push(Change { + (self.cb)(Change { path: json_path.to_owned(), change: ChangeKind::RequiredAdd { property: added.clone(), diff --git a/src/lib.rs b/src/lib.rs index e6729b7..16ec704 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,20 +14,20 @@ pub use types::*; /// /// `lhs` (left-hand side) is the old schema, `rhs` (right-hand side) is the new schema. pub fn diff(lhs: Value, rhs: Value) -> Result, Error> { - let changes = Vec::new(); let lhs_root: RootSchema = serde_json::from_value(lhs)?; let rhs_root: RootSchema = serde_json::from_value(rhs)?; - let mut walker = diff_walker::DiffWalker { - changes, - lhs_root, - rhs_root, + let mut changes = vec![]; + let cb = |change: Change| { + changes.push(change); }; + let mut walker = diff_walker::DiffWalker::new(Box::new(cb), lhs_root, rhs_root); walker.diff( "", &mut walker.lhs_root.schema.clone(), &mut walker.rhs_root.schema.clone(), )?; + drop(walker); - Ok(walker.changes) + Ok(changes) } From 35fa7c1af726821fd6f2ea3a398945eafc331bbb Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 17 May 2023 18:27:28 +0200 Subject: [PATCH 8/9] make generic --- src/diff_walker.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 5f871ae..2050f39 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -8,15 +8,16 @@ use serde_json::Value; use crate::{Change, ChangeKind, Error, JsonSchemaType, Range}; -pub struct DiffWalker<'cb> { - pub cb: Box, +pub struct DiffWalker { + pub cb: F, pub lhs_root: RootSchema, pub rhs_root: RootSchema, } -impl<'cb> DiffWalker<'cb> { + +impl DiffWalker { pub fn new( - cb: Box, + cb: F, lhs_root: RootSchema, rhs_root: RootSchema, ) -> Self { @@ -57,7 +58,7 @@ impl<'cb> DiffWalker<'cb> { let mut count = 0; let counter = |_change: Change| count += 1; DiffWalker::new( - Box::new(counter), + Box::new(counter) as Box, self.lhs_root.clone(), self.rhs_root.clone(), ) From 6b315ace4745cb0b0308ad3da27da65489b49f69 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 17 May 2023 18:30:38 +0200 Subject: [PATCH 9/9] fmt --- src/diff_walker.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/diff_walker.rs b/src/diff_walker.rs index 2050f39..e63ad3b 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -14,13 +14,8 @@ pub struct DiffWalker { pub rhs_root: RootSchema, } - impl DiffWalker { - pub fn new( - cb: F, - lhs_root: RootSchema, - rhs_root: RootSchema, - ) -> Self { + pub fn new(cb: F, lhs_root: RootSchema, rhs_root: RootSchema) -> Self { Self { cb, lhs_root,