diff --git a/src/diff_walker.rs b/src/diff_walker.rs index ad2b47d..d8c6b48 100644 --- a/src/diff_walker.rs +++ b/src/diff_walker.rs @@ -387,6 +387,114 @@ impl DiffWalker { } } + fn diff_pattern(&mut self, json_path: &str, lhs: &mut SchemaObject, rhs: &mut SchemaObject) { + let lhs_pattern = &lhs.string().pattern; + let rhs_pattern = &rhs.string().pattern; + + match (lhs_pattern, rhs_pattern) { + (Some(lhs_pat), Some(rhs_pat)) if lhs_pat != rhs_pat => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::PatternChange { + old_pattern: lhs_pat.clone(), + new_pattern: rhs_pat.clone(), + }, + }); + } + (Some(removed_pat), None) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::PatternRemove { + removed: removed_pat.clone(), + }, + }); + } + (None, Some(added_pat)) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::PatternAdd { + added: added_pat.clone(), + }, + }); + } + _ => {} // No change or both None + } + } + + fn diff_min_length( + &mut self, + json_path: &str, + lhs: &mut SchemaObject, + rhs: &mut SchemaObject, + ) { + let lhs_min = lhs.string().min_length; + let rhs_min = rhs.string().min_length; + + match (lhs_min, rhs_min) { + (Some(lhs_val), Some(rhs_val)) if lhs_val != rhs_val => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MinLengthChange { + old_value: lhs_val, + new_value: rhs_val, + }, + }); + } + (Some(removed_val), None) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MinLengthRemove { + removed: removed_val, + }, + }); + } + (None, Some(added_val)) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MinLengthAdd { added: added_val }, + }); + } + _ => {} // No change or both None + } + } + + fn diff_max_length( + &mut self, + json_path: &str, + lhs: &mut SchemaObject, + rhs: &mut SchemaObject, + ) { + let lhs_max = lhs.string().max_length; + let rhs_max = rhs.string().max_length; + + match (lhs_max, rhs_max) { + (Some(lhs_val), Some(rhs_val)) if lhs_val != rhs_val => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MaxLengthChange { + old_value: lhs_val, + new_value: rhs_val, + }, + }); + } + (Some(removed_val), None) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MaxLengthRemove { + removed: removed_val, + }, + }); + } + (None, Some(added_val)) => { + (self.cb)(Change { + path: json_path.to_owned(), + change: ChangeKind::MaxLengthAdd { added: added_val }, + }); + } + _ => {} // No change or both None + } + } + fn resolve_references( &self, lhs: &mut SchemaObject, @@ -496,6 +604,9 @@ impl DiffWalker { } self.diff_const(json_path, lhs, rhs); self.diff_format(json_path, lhs, rhs); + self.diff_pattern(json_path, lhs, rhs); + self.diff_min_length(json_path, lhs, rhs); + self.diff_max_length(json_path, lhs, rhs); // If we split the types, we don't want to compare type-specific properties // because they are already compared in the `Self::diff_any_of` if !is_lhs_split && !is_rhs_split { diff --git a/src/types.rs b/src/types.rs index bf213f5..718170f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -124,6 +124,57 @@ pub enum ChangeKind { /// The new format value. new_format: String, }, + /// A pattern constraint has been added. + PatternAdd { + /// The pattern that was added. + added: String, + }, + /// A pattern constraint has been removed. + PatternRemove { + /// The pattern that was removed. + removed: String, + }, + /// A pattern constraint has been changed. + PatternChange { + /// The old pattern value. + old_pattern: String, + /// The new pattern value. + new_pattern: String, + }, + /// A minLength constraint has been added. + MinLengthAdd { + /// The minLength value that was added. + added: u32, + }, + /// A minLength constraint has been removed. + MinLengthRemove { + /// The minLength value that was removed. + removed: u32, + }, + /// A minLength constraint has been changed. + MinLengthChange { + /// The old minLength value. + old_value: u32, + /// The new minLength value. + new_value: u32, + }, + /// A maxLength constraint has been added. + MaxLengthAdd { + /// The maxLength value that was added. + added: u32, + }, + /// A maxLength constraint has been removed. + MaxLengthRemove { + /// The maxLength value that was removed. + removed: u32, + }, + /// A maxLength constraint has been changed. + MaxLengthChange { + /// The old maxLength value. + old_value: u32, + /// The new maxLength value. + new_value: u32, + }, } impl ChangeKind { @@ -170,6 +221,25 @@ impl ChangeKind { Self::FormatAdd { .. } => true, Self::FormatRemove { .. } => false, Self::FormatChange { .. } => true, + // Pattern changes are conservatively treated as breaking. + // Determining if one regex is a subset of another requires complex analysis. + Self::PatternAdd { .. } => true, + Self::PatternRemove { .. } => false, + Self::PatternChange { .. } => true, + // MinLength: increasing restricts (breaking), decreasing relaxes (non-breaking) + Self::MinLengthAdd { .. } => true, + Self::MinLengthRemove { .. } => false, + Self::MinLengthChange { + old_value, + new_value, + } => new_value > old_value, + // MaxLength: decreasing restricts (breaking), increasing relaxes (non-breaking) + Self::MaxLengthAdd { .. } => true, + Self::MaxLengthRemove { .. } => false, + Self::MaxLengthChange { + old_value, + new_value, + } => new_value < old_value, } } } diff --git a/tests/fixtures/max_length/max_length_add.json b/tests/fixtures/max_length/max_length_add.json new file mode 100644 index 0000000..b436eaa --- /dev/null +++ b/tests/fixtures/max_length/max_length_add.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string" }, + "rhs": { "type": "string", "maxLength": 10 } +} diff --git a/tests/fixtures/max_length/max_length_decrease.json b/tests/fixtures/max_length/max_length_decrease.json new file mode 100644 index 0000000..d67cad8 --- /dev/null +++ b/tests/fixtures/max_length/max_length_decrease.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "maxLength": 10 }, + "rhs": { "type": "string", "maxLength": 5 } +} diff --git a/tests/fixtures/max_length/max_length_increase.json b/tests/fixtures/max_length/max_length_increase.json new file mode 100644 index 0000000..68168f2 --- /dev/null +++ b/tests/fixtures/max_length/max_length_increase.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "maxLength": 5 }, + "rhs": { "type": "string", "maxLength": 10 } +} diff --git a/tests/fixtures/max_length/max_length_remove.json b/tests/fixtures/max_length/max_length_remove.json new file mode 100644 index 0000000..196a721 --- /dev/null +++ b/tests/fixtures/max_length/max_length_remove.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "maxLength": 10 }, + "rhs": { "type": "string" } +} diff --git a/tests/fixtures/max_length/max_length_unchanged.json b/tests/fixtures/max_length/max_length_unchanged.json new file mode 100644 index 0000000..7b09177 --- /dev/null +++ b/tests/fixtures/max_length/max_length_unchanged.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "maxLength": 10 }, + "rhs": { "type": "string", "maxLength": 10 } +} diff --git a/tests/fixtures/min_length/min_length_add.json b/tests/fixtures/min_length/min_length_add.json new file mode 100644 index 0000000..2eab2d2 --- /dev/null +++ b/tests/fixtures/min_length/min_length_add.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string" }, + "rhs": { "type": "string", "minLength": 5 } +} diff --git a/tests/fixtures/min_length/min_length_decrease.json b/tests/fixtures/min_length/min_length_decrease.json new file mode 100644 index 0000000..cf3f2fc --- /dev/null +++ b/tests/fixtures/min_length/min_length_decrease.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "minLength": 5 }, + "rhs": { "type": "string", "minLength": 3 } +} diff --git a/tests/fixtures/min_length/min_length_increase.json b/tests/fixtures/min_length/min_length_increase.json new file mode 100644 index 0000000..d9e786f --- /dev/null +++ b/tests/fixtures/min_length/min_length_increase.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "minLength": 3 }, + "rhs": { "type": "string", "minLength": 5 } +} diff --git a/tests/fixtures/min_length/min_length_remove.json b/tests/fixtures/min_length/min_length_remove.json new file mode 100644 index 0000000..039059f --- /dev/null +++ b/tests/fixtures/min_length/min_length_remove.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "minLength": 5 }, + "rhs": { "type": "string" } +} diff --git a/tests/fixtures/min_length/min_length_unchanged.json b/tests/fixtures/min_length/min_length_unchanged.json new file mode 100644 index 0000000..0996481 --- /dev/null +++ b/tests/fixtures/min_length/min_length_unchanged.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "minLength": 5 }, + "rhs": { "type": "string", "minLength": 5 } +} diff --git a/tests/fixtures/pattern/pattern_add.json b/tests/fixtures/pattern/pattern_add.json new file mode 100644 index 0000000..0a9c98b --- /dev/null +++ b/tests/fixtures/pattern/pattern_add.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string" }, + "rhs": { "type": "string", "pattern": "^[a-z]+$" } +} diff --git a/tests/fixtures/pattern/pattern_change.json b/tests/fixtures/pattern/pattern_change.json new file mode 100644 index 0000000..a8df6bd --- /dev/null +++ b/tests/fixtures/pattern/pattern_change.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "pattern": "^[a-z]+$" }, + "rhs": { "type": "string", "pattern": "^[A-Z]+$" } +} diff --git a/tests/fixtures/pattern/pattern_remove.json b/tests/fixtures/pattern/pattern_remove.json new file mode 100644 index 0000000..f5cd8a1 --- /dev/null +++ b/tests/fixtures/pattern/pattern_remove.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "pattern": "^[a-z]+$" }, + "rhs": { "type": "string" } +} diff --git a/tests/fixtures/pattern/pattern_unchanged.json b/tests/fixtures/pattern/pattern_unchanged.json new file mode 100644 index 0000000..94f325e --- /dev/null +++ b/tests/fixtures/pattern/pattern_unchanged.json @@ -0,0 +1,4 @@ +{ + "lhs": { "type": "string", "pattern": "^[a-z]+$" }, + "rhs": { "type": "string", "pattern": "^[a-z]+$" } +} diff --git a/tests/snapshots/test__from_fixtures@max_length__max_length_add.json.snap b/tests/snapshots/test__from_fixtures@max_length__max_length_add.json.snap new file mode 100644 index 0000000..368df38 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@max_length__max_length_add.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + type: string + rhs: + maxLength: 10 + type: string +input_file: tests/fixtures/max_length/max_length_add.json +--- +[ + Change { + path: "", + change: MaxLengthAdd { + added: 10, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@max_length__max_length_decrease.json.snap b/tests/snapshots/test__from_fixtures@max_length__max_length_decrease.json.snap new file mode 100644 index 0000000..383c3f1 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@max_length__max_length_decrease.json.snap @@ -0,0 +1,22 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + maxLength: 10 + type: string + rhs: + maxLength: 5 + type: string +input_file: tests/fixtures/max_length/max_length_decrease.json +--- +[ + Change { + path: "", + change: MaxLengthChange { + old_value: 10, + new_value: 5, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@max_length__max_length_increase.json.snap b/tests/snapshots/test__from_fixtures@max_length__max_length_increase.json.snap new file mode 100644 index 0000000..63f8a8d --- /dev/null +++ b/tests/snapshots/test__from_fixtures@max_length__max_length_increase.json.snap @@ -0,0 +1,22 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + maxLength: 5 + type: string + rhs: + maxLength: 10 + type: string +input_file: tests/fixtures/max_length/max_length_increase.json +--- +[ + Change { + path: "", + change: MaxLengthChange { + old_value: 5, + new_value: 10, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@max_length__max_length_remove.json.snap b/tests/snapshots/test__from_fixtures@max_length__max_length_remove.json.snap new file mode 100644 index 0000000..a5be677 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@max_length__max_length_remove.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + maxLength: 10 + type: string + rhs: + type: string +input_file: tests/fixtures/max_length/max_length_remove.json +--- +[ + Change { + path: "", + change: MaxLengthRemove { + removed: 10, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@max_length__max_length_unchanged.json.snap b/tests/snapshots/test__from_fixtures@max_length__max_length_unchanged.json.snap new file mode 100644 index 0000000..49bdc76 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@max_length__max_length_unchanged.json.snap @@ -0,0 +1,14 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + maxLength: 10 + type: string + rhs: + maxLength: 10 + type: string +input_file: tests/fixtures/max_length/max_length_unchanged.json +--- +[] diff --git a/tests/snapshots/test__from_fixtures@min_length__min_length_add.json.snap b/tests/snapshots/test__from_fixtures@min_length__min_length_add.json.snap new file mode 100644 index 0000000..17cbd56 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@min_length__min_length_add.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + type: string + rhs: + minLength: 5 + type: string +input_file: tests/fixtures/min_length/min_length_add.json +--- +[ + Change { + path: "", + change: MinLengthAdd { + added: 5, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@min_length__min_length_decrease.json.snap b/tests/snapshots/test__from_fixtures@min_length__min_length_decrease.json.snap new file mode 100644 index 0000000..65c3e57 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@min_length__min_length_decrease.json.snap @@ -0,0 +1,22 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + minLength: 5 + type: string + rhs: + minLength: 3 + type: string +input_file: tests/fixtures/min_length/min_length_decrease.json +--- +[ + Change { + path: "", + change: MinLengthChange { + old_value: 5, + new_value: 3, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@min_length__min_length_increase.json.snap b/tests/snapshots/test__from_fixtures@min_length__min_length_increase.json.snap new file mode 100644 index 0000000..3bfbb71 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@min_length__min_length_increase.json.snap @@ -0,0 +1,22 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + minLength: 3 + type: string + rhs: + minLength: 5 + type: string +input_file: tests/fixtures/min_length/min_length_increase.json +--- +[ + Change { + path: "", + change: MinLengthChange { + old_value: 3, + new_value: 5, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@min_length__min_length_remove.json.snap b/tests/snapshots/test__from_fixtures@min_length__min_length_remove.json.snap new file mode 100644 index 0000000..efe26fc --- /dev/null +++ b/tests/snapshots/test__from_fixtures@min_length__min_length_remove.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + minLength: 5 + type: string + rhs: + type: string +input_file: tests/fixtures/min_length/min_length_remove.json +--- +[ + Change { + path: "", + change: MinLengthRemove { + removed: 5, + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@min_length__min_length_unchanged.json.snap b/tests/snapshots/test__from_fixtures@min_length__min_length_unchanged.json.snap new file mode 100644 index 0000000..45ede58 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@min_length__min_length_unchanged.json.snap @@ -0,0 +1,14 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + minLength: 5 + type: string + rhs: + minLength: 5 + type: string +input_file: tests/fixtures/min_length/min_length_unchanged.json +--- +[] diff --git a/tests/snapshots/test__from_fixtures@pattern__pattern_add.json.snap b/tests/snapshots/test__from_fixtures@pattern__pattern_add.json.snap new file mode 100644 index 0000000..441c663 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@pattern__pattern_add.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + type: string + rhs: + pattern: "^[a-z]+$" + type: string +input_file: tests/fixtures/pattern/pattern_add.json +--- +[ + Change { + path: "", + change: PatternAdd { + added: "^[a-z]+$", + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@pattern__pattern_change.json.snap b/tests/snapshots/test__from_fixtures@pattern__pattern_change.json.snap new file mode 100644 index 0000000..5cd261c --- /dev/null +++ b/tests/snapshots/test__from_fixtures@pattern__pattern_change.json.snap @@ -0,0 +1,22 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + pattern: "^[a-z]+$" + type: string + rhs: + pattern: "^[A-Z]+$" + type: string +input_file: tests/fixtures/pattern/pattern_change.json +--- +[ + Change { + path: "", + change: PatternChange { + old_pattern: "^[a-z]+$", + new_pattern: "^[A-Z]+$", + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@pattern__pattern_remove.json.snap b/tests/snapshots/test__from_fixtures@pattern__pattern_remove.json.snap new file mode 100644 index 0000000..98a0645 --- /dev/null +++ b/tests/snapshots/test__from_fixtures@pattern__pattern_remove.json.snap @@ -0,0 +1,20 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + pattern: "^[a-z]+$" + type: string + rhs: + type: string +input_file: tests/fixtures/pattern/pattern_remove.json +--- +[ + Change { + path: "", + change: PatternRemove { + removed: "^[a-z]+$", + }, + }, +] diff --git a/tests/snapshots/test__from_fixtures@pattern__pattern_unchanged.json.snap b/tests/snapshots/test__from_fixtures@pattern__pattern_unchanged.json.snap new file mode 100644 index 0000000..30b786a --- /dev/null +++ b/tests/snapshots/test__from_fixtures@pattern__pattern_unchanged.json.snap @@ -0,0 +1,14 @@ +--- +source: tests/test.rs +assertion_line: 12 +expression: diff +info: + lhs: + pattern: "^[a-z]+$" + type: string + rhs: + pattern: "^[a-z]+$" + type: string +input_file: tests/fixtures/pattern/pattern_unchanged.json +--- +[]