Skip to content

Commit 47ed7cd

Browse files
committed
compare anyOf based on handmade diff score
1 parent 6c59eec commit 47ed7cd

8 files changed

+236
-8
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ schemars = { version = "0.8.12", default_features = false }
2525
serde = "1.0.158"
2626
serde_json = "1.0.94"
2727
thiserror = "1.0.40"
28+
hungarian = "1.1.1"
2829

2930
[features]
3031
build-binary = ["clap", "anyhow"]

src/diff_walker.rs

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
22

33
use schemars::schema::{
44
InstanceType, NumberValidation, ObjectValidation, RootSchema, Schema, SchemaObject,
5-
SingleOrVec, SubschemaValidation,
5+
SingleOrVec, StringValidation, SubschemaValidation,
66
};
77
use serde_json::Value;
88

@@ -14,6 +14,81 @@ pub struct DiffWalker {
1414
pub rhs_root: RootSchema,
1515
}
1616

17+
trait DiffScore {
18+
fn diff_score(&mut self, rhs: &mut Self) -> usize;
19+
}
20+
21+
impl DiffScore for Schema {
22+
fn diff_score(&mut self, rhs: &mut Self) -> usize {
23+
self.clone()
24+
.into_object()
25+
.diff_score(&mut rhs.clone().into_object())
26+
}
27+
}
28+
impl DiffScore for SchemaObject {
29+
fn diff_score(&mut self, rhs: &mut Self) -> usize {
30+
let mut score = 0;
31+
if self.effective_type() != rhs.effective_type() {
32+
score += 10;
33+
}
34+
score += self.number().diff_score(rhs.number())
35+
+ self.string().diff_score(rhs.string())
36+
+ self.object().diff_score(rhs.object());
37+
score
38+
}
39+
}
40+
41+
impl DiffScore for NumberValidation {
42+
fn diff_score(&mut self, rhs: &mut Self) -> usize {
43+
let mut score = 0;
44+
if self.multiple_of != rhs.multiple_of {
45+
score += 1;
46+
}
47+
if self.minimum != rhs.minimum {
48+
score += 1;
49+
}
50+
if self.maximum != rhs.maximum {
51+
score += 1;
52+
}
53+
score
54+
}
55+
}
56+
57+
impl DiffScore for StringValidation {
58+
fn diff_score(&mut self, rhs: &mut Self) -> usize {
59+
let mut score = 0;
60+
if self.pattern != rhs.pattern {
61+
score += 1;
62+
}
63+
if self.min_length != rhs.min_length {
64+
score += 1;
65+
}
66+
if self.max_length != rhs.max_length {
67+
score += 1;
68+
}
69+
score
70+
}
71+
}
72+
73+
impl DiffScore for ObjectValidation {
74+
fn diff_score(&mut self, rhs: &mut Self) -> usize {
75+
let mut score = 0;
76+
if self.required != rhs.required {
77+
score += 1;
78+
}
79+
if self.properties != rhs.properties {
80+
score += 1;
81+
}
82+
if self.pattern_properties != rhs.pattern_properties {
83+
score += 1;
84+
}
85+
if self.additional_properties != rhs.additional_properties {
86+
score += 1;
87+
}
88+
score
89+
}
90+
}
91+
1792
impl DiffWalker {
1893
fn diff_any_of(
1994
&mut self,
@@ -29,25 +104,36 @@ impl DiffWalker {
29104
{
30105
match (lhs_any_of.len(), rhs_any_of.len()) {
31106
(l, r) if l <= r => {
32-
lhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); r - l]);
107+
lhs_any_of.append(&mut vec![Schema::Bool(false); r - l]);
33108
}
34109
(l, r) => {
35-
rhs_any_of.append(&mut vec![Schema::Object(SchemaObject::default()); l - r]);
110+
rhs_any_of.append(&mut vec![Schema::Bool(false); l - r]);
36111
}
37112
}
38113

39-
for (i, (lhs_inner, rhs_inner)) in
40-
lhs_any_of.iter_mut().zip(rhs_any_of.iter_mut()).enumerate()
41-
{
114+
let mut mat = vec![];
115+
let len = lhs_any_of.len();
116+
lhs_any_of.iter_mut().for_each(|l| {
117+
rhs_any_of
118+
.iter_mut()
119+
.for_each(|r| mat.push(l.diff_score(r)))
120+
});
121+
let pairs = hungarian::minimize(&mat, len, len)
122+
.into_iter()
123+
.enumerate()
124+
.filter_map(|(i, j)| j.map(|j| (i, j)))
125+
.collect::<Vec<_>>();
126+
127+
for i in 0..len {
42128
let new_path = match is_rhs_split {
43129
true => json_path.to_owned(),
44130
false => format!("{json_path}.<anyOf:{i}>"),
45131
};
46132
self.do_diff(
47133
&new_path,
48134
true,
49-
&mut lhs_inner.clone().into_object(),
50-
&mut rhs_inner.clone().into_object(),
135+
&mut lhs_any_of[pairs[i].0].clone().into_object(),
136+
&mut rhs_any_of[pairs[i].1].clone().into_object(),
51137
)?;
52138
}
53139
}

tests/fixtures/any_of/objects_1.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"lhs": {
3+
"anyOf": [
4+
{"properties": {"foo": {}}},
5+
{"properties": {"type": {"const": "bar"}}}
6+
]
7+
},
8+
"rhs": {
9+
"anyOf": [
10+
{
11+
"title": "replay_recording",
12+
"type": "object",
13+
"properties": {"foo": {}}
14+
},
15+
{"properties": {"type": {"const": "bar"}}}
16+
]
17+
}
18+
}

tests/fixtures/any_of/objects_2.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"lhs": {
3+
"anyOf": [
4+
{"properties": {"foo": {}}},
5+
{"properties": {"type": {"const": "bar"}}}
6+
]
7+
},
8+
"rhs": {
9+
"anyOf": [
10+
{ "type": "boolean" },
11+
{
12+
"title": "replay_recording",
13+
"type": "object",
14+
"properties": {"foo": {}}
15+
},
16+
{"properties": {"type": {"const": "bar"}}}
17+
]
18+
}
19+
}

tests/fixtures/any_of/objects_3.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"lhs": {
3+
"anyOf": [
4+
{ "type": "boolean" },
5+
{"properties": {"foo": {}}},
6+
{"properties": {"type": {"const": "bar"}}}
7+
]
8+
},
9+
"rhs": {
10+
"anyOf": [
11+
{
12+
"title": "replay_recording",
13+
"type": "object",
14+
"properties": {"foo": {}}
15+
},
16+
{"properties": {"type": {"const": "bar"}}}
17+
]
18+
}
19+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
source: tests/test.rs
3+
expression: diff
4+
info:
5+
lhs:
6+
anyOf:
7+
- properties:
8+
foo: {}
9+
- properties:
10+
type:
11+
const: bar
12+
rhs:
13+
anyOf:
14+
- properties:
15+
foo: {}
16+
title: replay_recording
17+
type: object
18+
- properties:
19+
type:
20+
const: bar
21+
input_file: tests/fixtures/any_of/objects_1.json
22+
---
23+
[]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: tests/test.rs
3+
expression: diff
4+
info:
5+
lhs:
6+
anyOf:
7+
- properties:
8+
foo: {}
9+
- properties:
10+
type:
11+
const: bar
12+
rhs:
13+
anyOf:
14+
- type: boolean
15+
- properties:
16+
foo: {}
17+
title: replay_recording
18+
type: object
19+
- properties:
20+
type:
21+
const: bar
22+
input_file: tests/fixtures/any_of/objects_2.json
23+
---
24+
[
25+
Change {
26+
path: "",
27+
change: TypeAdd {
28+
added: Boolean,
29+
},
30+
},
31+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: tests/test.rs
3+
expression: diff
4+
info:
5+
lhs:
6+
anyOf:
7+
- type: boolean
8+
- properties:
9+
foo: {}
10+
- properties:
11+
type:
12+
const: bar
13+
rhs:
14+
anyOf:
15+
- properties:
16+
foo: {}
17+
title: replay_recording
18+
type: object
19+
- properties:
20+
type:
21+
const: bar
22+
input_file: tests/fixtures/any_of/objects_3.json
23+
---
24+
[
25+
Change {
26+
path: "",
27+
change: TypeRemove {
28+
removed: Boolean,
29+
},
30+
},
31+
]

0 commit comments

Comments
 (0)