Skip to content

Commit 143601b

Browse files
authored
support exclusive keywords (#35)
1 parent 19d0fd5 commit 143601b

28 files changed

+510
-93
lines changed

src/diff_walker.rs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::{BTreeMap, BTreeSet};
2+
use std::mem::discriminant;
23

34
use schemars::schema::{
45
InstanceType, NumberValidation, ObjectValidation, RootSchema, Schema, SchemaObject,
@@ -206,45 +207,51 @@ impl<F: FnMut(Change)> DiffWalker<F> {
206207
lhs: &mut SchemaObject,
207208
rhs: &mut SchemaObject,
208209
) -> Result<(), Error> {
209-
let diff = |lhs, rhs, range| match (lhs, rhs) {
210-
(None, Some(value)) => Some(Change {
211-
path: json_path.to_owned(),
212-
change: ChangeKind::RangeAdd {
213-
added: range,
214-
value,
215-
},
216-
}),
217-
(Some(value), None) => Some(Change {
210+
let mut diff = |lhs, rhs| match (lhs, rhs) {
211+
(None, Some(value)) => (self.cb)(Change {
218212
path: json_path.to_owned(),
219-
change: ChangeKind::RangeRemove {
220-
removed: range,
221-
value,
222-
},
213+
change: ChangeKind::RangeAdd { added: value },
223214
}),
224-
(Some(lhs), Some(rhs)) if lhs != rhs => Some(Change {
215+
(Some(value), None) => (self.cb)(Change {
225216
path: json_path.to_owned(),
226-
change: ChangeKind::RangeChange {
227-
changed: range,
228-
old_value: lhs,
229-
new_value: rhs,
230-
},
217+
change: ChangeKind::RangeRemove { removed: value },
231218
}),
232-
_ => None,
219+
(Some(lhs), Some(rhs))
220+
if (lhs != rhs && discriminant(&lhs) == discriminant(&rhs))
221+
|| discriminant(&lhs) != discriminant(&rhs) =>
222+
{
223+
(self.cb)(Change {
224+
path: json_path.to_owned(),
225+
change: ChangeKind::RangeChange {
226+
old_value: lhs,
227+
new_value: rhs,
228+
},
229+
})
230+
}
231+
_ => (),
233232
};
234-
if let Some(diff) = diff(
235-
lhs.number_validation().minimum,
236-
rhs.number_validation().minimum,
237-
Range::Minimum,
233+
let choose_min = |schema: &mut SchemaObject| match (
234+
schema.number_validation().minimum,
235+
schema.number_validation().exclusive_minimum,
238236
) {
239-
(self.cb)(diff)
240-
}
241-
if let Some(diff) = diff(
242-
lhs.number_validation().maximum,
243-
rhs.number_validation().maximum,
244-
Range::Maximum,
237+
(Some(min), None) => Some(Range::Minimum(min)),
238+
(None, Some(exc)) => Some(Range::ExclusiveMinimum(exc)),
239+
(Some(min), Some(exc)) if min <= exc => Some(Range::ExclusiveMinimum(exc)),
240+
(Some(min), Some(exc)) if min > exc => Some(Range::Minimum(min)),
241+
_ => None,
242+
};
243+
let choose_max = |schema: &mut SchemaObject| match (
244+
schema.number_validation().maximum,
245+
schema.number_validation().exclusive_maximum,
245246
) {
246-
(self.cb)(diff)
247-
}
247+
(Some(max), None) => Some(Range::Maximum(max)),
248+
(None, Some(exc)) => Some(Range::ExclusiveMaximum(exc)),
249+
(Some(max), Some(exc)) if max >= exc => Some(Range::ExclusiveMaximum(exc)),
250+
(Some(max), Some(exc)) if max < exc => Some(Range::Maximum(max)),
251+
_ => None,
252+
};
253+
diff(choose_min(lhs), choose_min(rhs));
254+
diff(choose_max(lhs), choose_max(rhs));
248255
Ok(())
249256
}
250257

src/types.rs

Lines changed: 135 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,20 @@ pub enum ChangeKind {
5454
},
5555
/// A minimum/maximum constraint has been added.
5656
RangeAdd {
57-
/// The name of the added constraint.
58-
added: Range,
5957
/// The value of the added constraint.
60-
value: f64,
58+
added: Range,
6159
},
6260
/// A minimum/maximum constraint has been removed.
6361
RangeRemove {
64-
/// The name of the removed constraint.
65-
removed: Range,
6662
/// The value of the removed constraint.
67-
value: f64,
63+
removed: Range,
6864
},
6965
/// A minimum/maximum constraint has been updated.
7066
RangeChange {
71-
/// The name of the changed constraint.
72-
changed: Range,
7367
/// The old constraint value.
74-
old_value: f64,
68+
old_value: Range,
7569
/// The new constraint value.
76-
new_value: f64,
70+
new_value: Range,
7771
},
7872
/// An array-type item has been changed from tuple validation to array validation.
7973
///
@@ -140,13 +134,16 @@ impl ChangeKind {
140134
Self::RangeAdd { .. } => true,
141135
Self::RangeRemove { .. } => false,
142136
Self::RangeChange {
143-
changed,
144-
old_value: lhs_value,
145-
new_value: rhs_value,
146-
} => match changed {
147-
Range::Minimum if lhs_value < rhs_value => true,
148-
Range::Maximum if lhs_value > rhs_value => true,
149-
_ => false,
137+
old_value,
138+
new_value,
139+
} => match (old_value, new_value) {
140+
(Range::ExclusiveMinimum(exc), Range::Minimum(min)) if exc >= min => false,
141+
(Range::ExclusiveMaximum(exc), Range::Maximum(max)) if exc <= max => false,
142+
(Range::Minimum(l), Range::Minimum(r)) if l >= r => false,
143+
(Range::ExclusiveMinimum(l), Range::ExclusiveMinimum(r)) if l >= r => false,
144+
(Range::Maximum(l), Range::Maximum(r)) if l <= r => false,
145+
(Range::ExclusiveMaximum(l), Range::ExclusiveMaximum(r)) if l <= r => false,
146+
_ => true,
150147
},
151148
Self::TupleToArray { .. } => false,
152149
Self::ArrayToTuple { .. } => true,
@@ -217,11 +214,127 @@ impl From<InstanceType> for JsonSchemaType {
217214
}
218215

219216
/// Range constraints in JSON schema.
220-
#[derive(Serialize, Clone, Ord, Eq, PartialEq, PartialOrd, Debug)]
217+
#[derive(Serialize, Clone, PartialEq, PartialOrd, Debug)]
218+
#[serde(rename_all = "camelCase")]
221219
#[allow(missing_docs)]
222220
pub enum Range {
223-
#[serde(rename = "minimum")]
224-
Minimum,
225-
#[serde(rename = "maximum")]
226-
Maximum,
221+
Minimum(f64),
222+
Maximum(f64),
223+
ExclusiveMinimum(f64),
224+
ExclusiveMaximum(f64),
225+
}
226+
227+
#[cfg(test)]
228+
mod tests {
229+
use super::*;
230+
#[test]
231+
fn is_range_change_breaking() {
232+
assert!(!ChangeKind::RangeChange {
233+
old_value: Range::Minimum(1.0),
234+
new_value: Range::Minimum(1.0),
235+
}
236+
.is_breaking());
237+
238+
assert!(ChangeKind::RangeChange {
239+
old_value: Range::Minimum(1.0),
240+
new_value: Range::Minimum(2.0),
241+
}
242+
.is_breaking());
243+
244+
assert!(!ChangeKind::RangeChange {
245+
old_value: Range::Minimum(2.0),
246+
new_value: Range::Minimum(1.0),
247+
}
248+
.is_breaking());
249+
250+
assert!(ChangeKind::RangeChange {
251+
old_value: Range::Minimum(1.0),
252+
new_value: Range::ExclusiveMinimum(1.0),
253+
}
254+
.is_breaking());
255+
256+
assert!(ChangeKind::RangeChange {
257+
old_value: Range::Minimum(1.0),
258+
new_value: Range::ExclusiveMinimum(2.0),
259+
}
260+
.is_breaking());
261+
262+
assert!(ChangeKind::RangeChange {
263+
old_value: Range::Minimum(2.0),
264+
new_value: Range::ExclusiveMinimum(1.0),
265+
}
266+
.is_breaking());
267+
268+
assert!(!ChangeKind::RangeChange {
269+
old_value: Range::ExclusiveMinimum(1.0),
270+
new_value: Range::ExclusiveMinimum(1.0),
271+
}
272+
.is_breaking());
273+
274+
assert!(ChangeKind::RangeChange {
275+
old_value: Range::ExclusiveMinimum(1.0),
276+
new_value: Range::ExclusiveMinimum(2.0),
277+
}
278+
.is_breaking());
279+
280+
assert!(!ChangeKind::RangeChange {
281+
old_value: Range::ExclusiveMinimum(2.0),
282+
new_value: Range::ExclusiveMinimum(1.0),
283+
}
284+
.is_breaking());
285+
286+
assert!(!ChangeKind::RangeChange {
287+
old_value: Range::Maximum(1.0),
288+
new_value: Range::Maximum(1.0),
289+
}
290+
.is_breaking());
291+
292+
assert!(!ChangeKind::RangeChange {
293+
old_value: Range::Maximum(1.0),
294+
new_value: Range::Maximum(2.0),
295+
}
296+
.is_breaking());
297+
298+
assert!(ChangeKind::RangeChange {
299+
old_value: Range::Maximum(2.0),
300+
new_value: Range::Maximum(1.0),
301+
}
302+
.is_breaking());
303+
304+
assert!(ChangeKind::RangeChange {
305+
old_value: Range::Maximum(1.0),
306+
new_value: Range::ExclusiveMaximum(1.0),
307+
}
308+
.is_breaking());
309+
310+
assert!(ChangeKind::RangeChange {
311+
old_value: Range::Maximum(1.0),
312+
new_value: Range::ExclusiveMaximum(2.0),
313+
}
314+
.is_breaking());
315+
316+
assert!(ChangeKind::RangeChange {
317+
old_value: Range::Maximum(2.0),
318+
new_value: Range::ExclusiveMaximum(1.0),
319+
}
320+
.is_breaking());
321+
322+
assert!(!ChangeKind::RangeChange {
323+
old_value: Range::ExclusiveMaximum(1.0),
324+
new_value: Range::ExclusiveMaximum(1.0),
325+
}
326+
.is_breaking());
327+
328+
assert!(!ChangeKind::RangeChange {
329+
old_value: Range::ExclusiveMaximum(1.0),
330+
new_value: Range::ExclusiveMaximum(2.0),
331+
}
332+
.is_breaking());
333+
334+
assert!(ChangeKind::RangeChange {
335+
old_value: Range::ExclusiveMaximum(2.0),
336+
new_value: Range::ExclusiveMaximum(1.0),
337+
}
338+
.is_breaking());
339+
}
227340
}

tests/fixtures/range/all.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lhs": {
3+
"minimum": 1,
4+
"exclusiveMaximum": 100.0
5+
},
6+
"rhs": {
7+
"exclusiveMinimum": 3,
8+
"maximum": 30.0
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"minimum": 1.0
5+
},
6+
"rhs": {
7+
"type": "number",
8+
"exclusiveMaximum": 1.0
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"minimum": 1.0,
5+
"exclusiveMinimum": 1.0
6+
},
7+
"rhs": {
8+
"type": "number",
9+
"exclusiveMinimum": 0.4
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"minimum": 1.0,
5+
"exclusiveMinimum": 2.0
6+
},
7+
"rhs": {
8+
"type": "number",
9+
"exclusiveMinimum": 0.4
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"minimum": 1.0,
5+
"exclusiveMinimum": 0.5
6+
},
7+
"rhs": {
8+
"type": "number",
9+
"exclusiveMinimum": 0.4
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"maximum": 1.0
5+
},
6+
"rhs": {
7+
"type": "number",
8+
"exclusiveMaximum": 1.0
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"lhs": {
3+
"type": "number",
4+
"minimum": 1.0
5+
},
6+
"rhs": {
7+
"type": "number",
8+
"exclusiveMinimum": 1.0
9+
}
10+
}

tests/snapshots/test__from_fixtures@any_of__any_of_with_constraint_to_type_1.json.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ input_file: tests/fixtures/any_of/any_of_with_constraint_to_type_1.json
1616
Change {
1717
path: "",
1818
change: RangeRemove {
19-
removed: Minimum,
20-
value: 1.0,
19+
removed: Minimum(
20+
1.0,
21+
),
2122
},
2223
},
2324
Change {

tests/snapshots/test__from_fixtures@any_of__any_of_with_constraint_to_type_2.json.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ input_file: tests/fixtures/any_of/any_of_with_constraint_to_type_2.json
1919
Change {
2020
path: "",
2121
change: RangeRemove {
22-
removed: Minimum,
23-
value: 2.0,
22+
removed: Minimum(
23+
2.0,
24+
),
2425
},
2526
},
2627
Change {

0 commit comments

Comments
 (0)