From 0930489e568f68e705f5304b208d6e6f2b1d9439 Mon Sep 17 00:00:00 2001 From: Aliaksandr Pinchuk Date: Tue, 18 Mar 2025 20:39:35 +0100 Subject: [PATCH] Add support for 'uniqueItems' schema diff detection --- .../schemadiffresult/SchemaDiffResult.java | 1 + .../openapidiff/core/model/ChangedSchema.java | 17 ++++++ .../core/model/schema/ChangedUniqueItems.java | 58 +++++++++++++++++++ .../openapidiff/core/SchemaDiffTest.java | 40 ++++++++++++- .../schemaDiff/schema-uniqueItems-diff-1.yaml | 31 ++++++++++ .../schemaDiff/schema-uniqueItems-diff-2.yaml | 31 ++++++++++ 6 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedUniqueItems.java create mode 100644 core/src/test/resources/schemaDiff/schema-uniqueItems-diff-1.yaml create mode 100644 core/src/test/resources/schemaDiff/schema-uniqueItems-diff-2.yaml diff --git a/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java b/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java index 698eade4..32cb5bee 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java @@ -75,6 +75,7 @@ public , X> DeferredChanged diff( context)) .setMultipleOf(new ChangedMultipleOf(left.getMultipleOf(), right.getMultipleOf())) .setNullable(new ChangedNullable(left.getNullable(), right.getNullable())) + .setUniqueItems(new ChangedUniqueItems(left.getUniqueItems(), right.getUniqueItems())) .setExamples(new ChangedExamples(left.getExamples(), right.getExamples())) .setExample(new ChangedExample(left.getExample(), right.getExample())) .setMaxItems(new ChangedMaxItems(left.getMaxItems(), right.getMaxItems(), context)) diff --git a/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java b/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java index db98a329..3779ee85 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java @@ -20,6 +20,7 @@ import org.openapitools.openapidiff.core.model.schema.ChangedOneOfSchema; import org.openapitools.openapidiff.core.model.schema.ChangedReadOnly; import org.openapitools.openapidiff.core.model.schema.ChangedRequired; +import org.openapitools.openapidiff.core.model.schema.ChangedUniqueItems; import org.openapitools.openapidiff.core.model.schema.ChangedWriteOnly; public class ChangedSchema implements ComposedChanged { @@ -50,6 +51,7 @@ public class ChangedSchema implements ComposedChanged { protected ChangedMaxProperties maxProperties; protected ChangedMinProperties minProperties; protected ChangedNullable nullable; + protected ChangedUniqueItems uniqueItems; protected boolean discriminatorPropertyChanged; protected ChangedSchema items; protected ChangedOneOfSchema oneOfSchema; @@ -138,6 +140,7 @@ public List getChangedElements() { maxProperties, minProperties, nullable, + uniqueItems, extensions)) .collect(Collectors.toList()); } @@ -313,6 +316,10 @@ public ChangedNullable getNullable() { return this.nullable; } + public ChangedUniqueItems getUniqueItems() { + return uniqueItems; + } + public boolean isDiscriminatorPropertyChanged() { return this.discriminatorPropertyChanged; } @@ -487,6 +494,12 @@ public ChangedSchema setNullable(final ChangedNullable nullable) { return this; } + public ChangedSchema setUniqueItems(ChangedUniqueItems uniqueItems) { + clearChangedCache(); + this.uniqueItems = uniqueItems; + return this; + } + public ChangedSchema setDiscriminatorPropertyChanged(final boolean discriminatorPropertyChanged) { clearChangedCache(); this.discriminatorPropertyChanged = discriminatorPropertyChanged; @@ -560,6 +573,7 @@ public boolean equals(Object o) { && Objects.equals(maxItems, that.maxItems) && Objects.equals(minItems, that.minItems) && Objects.equals(nullable, that.nullable) + && Objects.equals(uniqueItems, that.uniqueItems) && Objects.equals(items, that.items) && Objects.equals(oneOfSchema, that.oneOfSchema) && Objects.equals(addProp, that.addProp) @@ -596,6 +610,7 @@ public int hashCode() { maxItems, minItems, nullable, + uniqueItems, discriminatorPropertyChanged, items, oneOfSchema, @@ -657,6 +672,8 @@ public java.lang.String toString() { + this.getMinItems() + ", nullable=" + this.getNullable() + + ", uniqueItems=" + + this.getUniqueItems() + ", discriminatorPropertyChanged=" + this.isDiscriminatorPropertyChanged() + ", items=" diff --git a/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedUniqueItems.java b/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedUniqueItems.java new file mode 100644 index 00000000..ecccdf67 --- /dev/null +++ b/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedUniqueItems.java @@ -0,0 +1,58 @@ +package org.openapitools.openapidiff.core.model.schema; + +import java.util.Objects; +import org.openapitools.openapidiff.core.model.Changed; +import org.openapitools.openapidiff.core.model.DiffResult; + +public class ChangedUniqueItems implements Changed { + + private final Boolean left; + private final Boolean right; + + public ChangedUniqueItems(Boolean leftNullable, Boolean rightNullable) { + this.left = leftNullable; + this.right = rightNullable; + } + + @Override + public DiffResult isChanged() { + boolean leftValue = left != null && left; + boolean rightValue = right != null && right; + + if (leftValue == false && rightValue == true) { + return DiffResult.INCOMPATIBLE; + } + + if (leftValue == true && rightValue == false) { + return DiffResult.COMPATIBLE; + } + + return DiffResult.NO_CHANGES; + } + + public Boolean getLeft() { + return left; + } + + public Boolean getRight() { + return right; + } + + @Override + public String toString() { + return "ChangedUniqueItems [left=" + left + ", right=" + right + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChangedNullable that = (ChangedNullable) o; + return Objects.equals(left, that.getLeft()) && Objects.equals(right, that.getRight()); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } +} diff --git a/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java b/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java index 3915789b..17014e86 100644 --- a/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java +++ b/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java @@ -171,7 +171,7 @@ public void changeMinMaxItemsHandling() { } @Test // issue #482 - public void changeNullabeHandling() { + public void changeNullableHandling() { ChangedOpenApi changedOpenApi = OpenApiCompare.fromLocations( "schemaDiff/schema-nullable-diff-1.yaml", "schemaDiff/schema-nullable-diff-2.yaml"); @@ -201,6 +201,44 @@ public void changeNullabeHandling() { assertThat(props.get("field3").getNullable().getRight()).isTrue(); } + @Test // issue #478 + public void changeUniqueItemsHandling() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/schema-uniqueItems-diff-1.yaml", + "schemaDiff/schema-uniqueItems-diff-2.yaml"); + ChangedSchema changedSchema = + getRequestBodyChangedSchema( + changedOpenApi, POST, "/schema/uniqueItems", "application/json"); + + assertThat(changedSchema).isNotNull(); + Map props = changedSchema.getChangedProperties(); + assertThat(props).isNotEmpty(); + + // Check no changes in uniqueItems + assertThat(props.get("field0")).isNull(); + + // Check changes true -> false + assertThat(props.get("field1").getUniqueItems().isCompatible()).isTrue(); + assertThat(props.get("field1").getUniqueItems().getLeft()).isTrue(); + assertThat(props.get("field1").getUniqueItems().getRight()).isFalse(); + + // Check changes false -> true + assertThat(props.get("field2").getUniqueItems().isIncompatible()).isTrue(); + assertThat(props.get("field2").getUniqueItems().getLeft()).isFalse(); + assertThat(props.get("field2").getUniqueItems().getRight()).isTrue(); + + // Check deletion of uniqueItems + assertThat(props.get("field3").getUniqueItems().isCompatible()).isTrue(); + assertThat(props.get("field3").getUniqueItems().getLeft()).isTrue(); + assertThat(props.get("field3").getUniqueItems().getRight()).isNull(); + + // Check addition of uniqueItems + assertThat(props.get("field4").getUniqueItems().isIncompatible()).isTrue(); + assertThat(props.get("field4").getUniqueItems().getLeft()).isNull(); + assertThat(props.get("field4").getUniqueItems().getRight()).isTrue(); + } + @Test // issue #479 public void changeMinMaxPropertiesHandling() { ChangedOpenApi changedOpenApi = diff --git a/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-1.yaml b/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-1.yaml new file mode 100644 index 00000000..dc0e60de --- /dev/null +++ b/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-1.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.1 +info: + description: Schema diff uniqueItems + title: schema diff uniqueItems + version: 1.0.0 +paths: + /schema/uniqueItems: + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TestDTO' +components: + schemas: + TestDTO: + type: object + properties: + field0: + type: integer + field1: + type: integer + uniqueItems: true + field2: + type: integer + uniqueItems: false + field3: + type: integer + uniqueItems: true + field4: + type: integer \ No newline at end of file diff --git a/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-2.yaml b/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-2.yaml new file mode 100644 index 00000000..12e2b906 --- /dev/null +++ b/core/src/test/resources/schemaDiff/schema-uniqueItems-diff-2.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.1 +info: + description: Schema diff uniqueItems + title: schema diff uniqueItems + version: 1.0.0 +paths: + /schema/uniqueItems: + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TestDTO' +components: + schemas: + TestDTO: + type: object + properties: + field0: + type: integer + field1: + type: integer + uniqueItems: false + field2: + type: integer + uniqueItems: true + field3: + type: integer + field4: + type: integer + uniqueItems: true \ No newline at end of file