Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 39 additions & 32 deletions src/diff_walker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::mem::discriminant;

use schemars::schema::{
InstanceType, NumberValidation, ObjectValidation, RootSchema, Schema, SchemaObject,
Expand Down Expand Up @@ -206,45 +207,51 @@ impl<F: FnMut(Change)> DiffWalker<F> {
lhs: &mut SchemaObject,
rhs: &mut SchemaObject,
) -> Result<(), Error> {
let diff = |lhs, rhs, range| match (lhs, rhs) {
(None, Some(value)) => Some(Change {
path: json_path.to_owned(),
change: ChangeKind::RangeAdd {
added: range,
value,
},
}),
(Some(value), None) => Some(Change {
let mut diff = |lhs, rhs| match (lhs, rhs) {
(None, Some(value)) => (self.cb)(Change {
path: json_path.to_owned(),
change: ChangeKind::RangeRemove {
removed: range,
value,
},
change: ChangeKind::RangeAdd { added: value },
}),
(Some(lhs), Some(rhs)) if lhs != rhs => Some(Change {
(Some(value), None) => (self.cb)(Change {
path: json_path.to_owned(),
change: ChangeKind::RangeChange {
changed: range,
old_value: lhs,
new_value: rhs,
},
change: ChangeKind::RangeRemove { removed: value },
}),
_ => None,
(Some(lhs), Some(rhs))
if (lhs != rhs && discriminant(&lhs) == discriminant(&rhs))
|| discriminant(&lhs) != discriminant(&rhs) =>
{
(self.cb)(Change {
path: json_path.to_owned(),
change: ChangeKind::RangeChange {
old_value: lhs,
new_value: rhs,
},
})
}
_ => (),
};
if let Some(diff) = diff(
lhs.number_validation().minimum,
rhs.number_validation().minimum,
Range::Minimum,
let choose_min = |schema: &mut SchemaObject| match (
schema.number_validation().minimum,
schema.number_validation().exclusive_minimum,
) {
(self.cb)(diff)
}
if let Some(diff) = diff(
lhs.number_validation().maximum,
rhs.number_validation().maximum,
Range::Maximum,
(Some(min), None) => Some(Range::Minimum(min)),
(None, Some(exc)) => Some(Range::ExclusiveMinimum(exc)),
(Some(min), Some(exc)) if min <= exc => Some(Range::ExclusiveMinimum(exc)),
(Some(min), Some(exc)) if min > exc => Some(Range::Minimum(min)),
_ => None,
};
let choose_max = |schema: &mut SchemaObject| match (
schema.number_validation().maximum,
schema.number_validation().exclusive_maximum,
) {
(self.cb)(diff)
}
(Some(max), None) => Some(Range::Maximum(max)),
(None, Some(exc)) => Some(Range::ExclusiveMaximum(exc)),
(Some(max), Some(exc)) if max >= exc => Some(Range::ExclusiveMaximum(exc)),
(Some(max), Some(exc)) if max < exc => Some(Range::Maximum(max)),
_ => None,
};
diff(choose_min(lhs), choose_min(rhs));
diff(choose_max(lhs), choose_max(rhs));
Ok(())
}

Expand Down
157 changes: 135 additions & 22 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,20 @@ pub enum ChangeKind {
},
/// A minimum/maximum constraint has been added.
RangeAdd {
/// The name of the added constraint.
added: Range,
/// The value of the added constraint.
value: f64,
added: Range,
},
/// A minimum/maximum constraint has been removed.
RangeRemove {
/// The name of the removed constraint.
removed: Range,
/// The value of the removed constraint.
value: f64,
removed: Range,
},
/// A minimum/maximum constraint has been updated.
RangeChange {
/// The name of the changed constraint.
changed: Range,
/// The old constraint value.
old_value: f64,
old_value: Range,
/// The new constraint value.
new_value: f64,
new_value: Range,
},
/// An array-type item has been changed from tuple validation to array validation.
///
Expand Down Expand Up @@ -140,13 +134,16 @@ impl ChangeKind {
Self::RangeAdd { .. } => true,
Self::RangeRemove { .. } => false,
Self::RangeChange {
changed,
old_value: lhs_value,
new_value: rhs_value,
} => match changed {
Range::Minimum if lhs_value < rhs_value => true,
Range::Maximum if lhs_value > rhs_value => true,
_ => false,
old_value,
new_value,
} => match (old_value, new_value) {
(Range::ExclusiveMinimum(exc), Range::Minimum(min)) if exc >= min => false,
(Range::ExclusiveMaximum(exc), Range::Maximum(max)) if exc <= max => false,
(Range::Minimum(l), Range::Minimum(r)) if l >= r => false,
(Range::ExclusiveMinimum(l), Range::ExclusiveMinimum(r)) if l >= r => false,
(Range::Maximum(l), Range::Maximum(r)) if l <= r => false,
(Range::ExclusiveMaximum(l), Range::ExclusiveMaximum(r)) if l <= r => false,
_ => true,
},
Self::TupleToArray { .. } => false,
Self::ArrayToTuple { .. } => true,
Expand Down Expand Up @@ -217,11 +214,127 @@ impl From<InstanceType> for JsonSchemaType {
}

/// Range constraints in JSON schema.
#[derive(Serialize, Clone, Ord, Eq, PartialEq, PartialOrd, Debug)]
#[derive(Serialize, Clone, PartialEq, PartialOrd, Debug)]
#[serde(rename_all = "camelCase")]
#[allow(missing_docs)]
pub enum Range {
#[serde(rename = "minimum")]
Minimum,
#[serde(rename = "maximum")]
Maximum,
Minimum(f64),
Maximum(f64),
ExclusiveMinimum(f64),
ExclusiveMaximum(f64),
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_range_change_breaking() {
assert!(!ChangeKind::RangeChange {
old_value: Range::Minimum(1.0),
new_value: Range::Minimum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Minimum(1.0),
new_value: Range::Minimum(2.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::Minimum(2.0),
new_value: Range::Minimum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Minimum(1.0),
new_value: Range::ExclusiveMinimum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Minimum(1.0),
new_value: Range::ExclusiveMinimum(2.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Minimum(2.0),
new_value: Range::ExclusiveMinimum(1.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::ExclusiveMinimum(1.0),
new_value: Range::ExclusiveMinimum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::ExclusiveMinimum(1.0),
new_value: Range::ExclusiveMinimum(2.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::ExclusiveMinimum(2.0),
new_value: Range::ExclusiveMinimum(1.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::Maximum(1.0),
new_value: Range::Maximum(1.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::Maximum(1.0),
new_value: Range::Maximum(2.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Maximum(2.0),
new_value: Range::Maximum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Maximum(1.0),
new_value: Range::ExclusiveMaximum(1.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Maximum(1.0),
new_value: Range::ExclusiveMaximum(2.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::Maximum(2.0),
new_value: Range::ExclusiveMaximum(1.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::ExclusiveMaximum(1.0),
new_value: Range::ExclusiveMaximum(1.0),
}
.is_breaking());

assert!(!ChangeKind::RangeChange {
old_value: Range::ExclusiveMaximum(1.0),
new_value: Range::ExclusiveMaximum(2.0),
}
.is_breaking());

assert!(ChangeKind::RangeChange {
old_value: Range::ExclusiveMaximum(2.0),
new_value: Range::ExclusiveMaximum(1.0),
}
.is_breaking());
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/range/all.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"lhs": {
"minimum": 1,
"exclusiveMaximum": 100.0
},
"rhs": {
"exclusiveMinimum": 3,
"maximum": 30.0
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/range/minimum_to_exclusive_maximum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"lhs": {
"type": "number",
"minimum": 1.0
},
"rhs": {
"type": "number",
"exclusiveMaximum": 1.0
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/range/redundant_minimum_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"lhs": {
"type": "number",
"minimum": 1.0,
"exclusiveMinimum": 1.0
},
"rhs": {
"type": "number",
"exclusiveMinimum": 0.4
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/range/redundant_minimum_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"lhs": {
"type": "number",
"minimum": 1.0,
"exclusiveMinimum": 2.0
},
"rhs": {
"type": "number",
"exclusiveMinimum": 0.4
}
}
11 changes: 11 additions & 0 deletions tests/fixtures/range/redundant_minimum_3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"lhs": {
"type": "number",
"minimum": 1.0,
"exclusiveMinimum": 0.5
},
"rhs": {
"type": "number",
"exclusiveMinimum": 0.4
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/range/to_exclusive_maximum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"lhs": {
"type": "number",
"maximum": 1.0
},
"rhs": {
"type": "number",
"exclusiveMaximum": 1.0
}
}
10 changes: 10 additions & 0 deletions tests/fixtures/range/to_exclusive_minimum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"lhs": {
"type": "number",
"minimum": 1.0
},
"rhs": {
"type": "number",
"exclusiveMinimum": 1.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ input_file: tests/fixtures/any_of/any_of_with_constraint_to_type_1.json
Change {
path: "",
change: RangeRemove {
removed: Minimum,
value: 1.0,
removed: Minimum(
1.0,
),
},
},
Change {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ input_file: tests/fixtures/any_of/any_of_with_constraint_to_type_2.json
Change {
path: "",
change: RangeRemove {
removed: Minimum,
value: 2.0,
removed: Minimum(
2.0,
),
},
},
Change {
Expand Down
Loading