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 1896783a..d6dd6a58 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 @@ -177,11 +177,20 @@ private DiffResult calculateCoreChanged() { && !discriminatorPropertyChanged) { return DiffResult.NO_CHANGES; } - if (changedType) { - if (SCHEMA_TYPE_CHANGED.enabled(context)) { - return DiffResult.INCOMPATIBLE; + if (changedType && SCHEMA_TYPE_CHANGED.enabled(context)) { + if (oldSchema != null && newSchema != null) { + if (context.isResponse() && hasMatchingType(oldSchema.getOneOf(), newSchema.getType())) { + return DiffResult.COMPATIBLE; + } + + if (context.isRequest() && hasMatchingType(newSchema.getOneOf(), oldSchema.getType())) { + return DiffResult.COMPATIBLE; + } } + + return DiffResult.INCOMPATIBLE; } + if (discriminatorPropertyChanged) { if (SCHEMA_DISCRIMINATOR_CHANGED.enabled(context)) { return DiffResult.INCOMPATIBLE; @@ -194,6 +203,14 @@ private DiffResult calculateCoreChanged() { return DiffResult.COMPATIBLE; } + @SuppressWarnings("rawtypes") + private boolean hasMatchingType(List schemas, String targetType) { + if (schemas == null || targetType == null) { + return false; + } + return schemas.stream().map(Schema::getType).anyMatch(targetType::equals); + } + private boolean compatibleForRequest() { if (context.isRequest()) { if (oldSchema == null && newSchema != null) { 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 1f41406a..e2ca1d02 100644 --- a/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java +++ b/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java @@ -438,4 +438,46 @@ public void testAllOfDiff() { assertThat(changedSchema.getRequired().getMissing()).containsExactly("fieldA"); assertThat(changedSchema.getRequired().getIncreased()).isEmpty(); } + + @Test + void testOneOfResponseDiffCompatibleChange() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/issue-798-one-of-response-diff-1.yaml", + "schemaDiff/issue-798-one-of-response-diff-2.yaml"); + + assertThat(changedOpenApi).isNotNull(); + assertThat(changedOpenApi.isChanged()).isEqualTo(DiffResult.COMPATIBLE); + } + + @Test + void testOneOfRequestDiffCompatibleChange() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/issue-798-one-of-request-diff-3.yaml", + "schemaDiff/issue-798-one-of-request-diff-4.yaml"); + + assertThat(changedOpenApi.isChanged()).isEqualTo(DiffResult.COMPATIBLE); + } + + @Test + void testOneOfResponseDiffIncompatibleChange() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/issue-798-one-of-response-diff-2.yaml", + "schemaDiff/issue-798-one-of-response-diff-1.yaml"); + + assertThat(changedOpenApi).isNotNull(); + assertThat(changedOpenApi.isChanged()).isEqualTo(DiffResult.INCOMPATIBLE); + } + + @Test + void testOneOfRequestDiffIncompatibleChange() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/issue-798-one-of-request-diff-4.yaml", + "schemaDiff/issue-798-one-of-request-diff-3.yaml"); + + assertThat(changedOpenApi.isChanged()).isEqualTo(DiffResult.INCOMPATIBLE); + } } diff --git a/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-3.yaml b/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-3.yaml new file mode 100644 index 00000000..637fafcd --- /dev/null +++ b/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-3.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.1 +info: + title: User Service + version: 1.0.0 +paths: + /users: + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: integer + required: + - name + required: true + responses: + 201: + description: Created + content: + application/json: + schema: + properties: + id: + type: integer + required: + - id + type: object \ No newline at end of file diff --git a/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-4.yaml b/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-4.yaml new file mode 100644 index 00000000..93bbfb86 --- /dev/null +++ b/core/src/test/resources/schemaDiff/issue-798-one-of-request-diff-4.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.1 +info: + title: User Service + version: 1.0.0 +paths: + /users: + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + oneOf: + - type: integer + - type: string + required: + - name + required: true + responses: + 201: + description: Created + content: + application/json: + schema: + properties: + id: + type: integer + required: + - id + type: object \ No newline at end of file diff --git a/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-1.yaml b/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-1.yaml new file mode 100644 index 00000000..30394f00 --- /dev/null +++ b/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-1.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.1 +info: + title: User Service + version: 1.0.0 +paths: + /users: + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: integer + required: + - name + required: true + responses: + 201: + description: Created + content: + application/json: + schema: + properties: + id: + oneOf: + - type: integer + - type: string + required: + - id + type: object \ No newline at end of file diff --git a/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-2.yaml b/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-2.yaml new file mode 100644 index 00000000..637fafcd --- /dev/null +++ b/core/src/test/resources/schemaDiff/issue-798-one-of-response-diff-2.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.1 +info: + title: User Service + version: 1.0.0 +paths: + /users: + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: integer + required: + - name + required: true + responses: + 201: + description: Created + content: + application/json: + schema: + properties: + id: + type: integer + required: + - id + type: object \ No newline at end of file