diff --git a/core/src/main/java/org/openapitools/openapidiff/core/compare/SchemaDiff.java b/core/src/main/java/org/openapitools/openapidiff/core/compare/SchemaDiff.java index 822160ede..1d1961bf7 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/compare/SchemaDiff.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/compare/SchemaDiff.java @@ -10,11 +10,13 @@ import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.XML; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import org.openapitools.openapidiff.core.compare.schemadiffresult.ArraySchemaDiffResult; import org.openapitools.openapidiff.core.compare.schemadiffresult.ComposedSchemaDiffResult; import org.openapitools.openapidiff.core.compare.schemadiffresult.SchemaDiffResult; @@ -79,17 +81,26 @@ public static SchemaDiffResult getSchemaDiffResult( } } - protected static Schema resolveComposedSchema(Components components, Schema schema) { + protected static Schema resolveComposedSchema( + Components components, Schema schema, Set visitedRefs) { if (schema instanceof ComposedSchema) { ComposedSchema composedSchema = (ComposedSchema) schema; - List allOfSchemaList = composedSchema.getAllOf(); - if (allOfSchemaList != null) { - for (Schema allOfSchema : allOfSchemaList) { - allOfSchema = refPointer.resolveRef(components, allOfSchema, allOfSchema.get$ref()); - allOfSchema = resolveComposedSchema(components, allOfSchema); - schema = addSchema(schema, allOfSchema); + List composedSchemas = new ArrayList<>(); + Optional.ofNullable(composedSchema.getAllOf()).ifPresent(composedSchemas::addAll); + Optional.ofNullable(composedSchema.getAnyOf()).ifPresent(composedSchemas::addAll); + + if (!composedSchemas.isEmpty()) { + for (Schema composed : composedSchemas) { + if (composed.get$ref() == null || !visitedRefs.contains(composed.get$ref())) { + Set updatedVisitedRefs = new HashSet<>(visitedRefs); + updatedVisitedRefs.add(composed.get$ref()); + composed = refPointer.resolveRef(components, composed, composed.get$ref()); + composed = resolveComposedSchema(components, composed, updatedVisitedRefs); + schema = addSchema(schema, composed); + } } composedSchema.setAllOf(null); + composedSchema.setAnyOf(null); } } return schema; @@ -154,6 +165,16 @@ protected static Schema addSchema(Schema schema, Schema fromSchema) { } schema.getExtensions().putAll(fromSchema.getExtensions()); } + if (fromSchema instanceof ComposedSchema && schema instanceof ComposedSchema) { + ComposedSchema composedFromSchema = (ComposedSchema) fromSchema; + ComposedSchema composedSchema = (ComposedSchema) schema; + if (composedFromSchema.getOneOf() != null) { + if (composedSchema.getOneOf() == null) { + composedSchema.setOneOf(new ArrayList<>()); + } + composedSchema.getOneOf().addAll(composedFromSchema.getOneOf()); + } + } if (fromSchema.getDiscriminator() != null) { if (schema.getDiscriminator() == null) { schema.setDiscriminator(new Discriminator()); @@ -316,8 +337,8 @@ public DeferredChanged computeDiffForReal( left = refPointer.resolveRef(this.leftComponents, left, getSchemaRef(left)); right = refPointer.resolveRef(this.rightComponents, right, getSchemaRef(right)); - left = resolveComposedSchema(leftComponents, left); - right = resolveComposedSchema(rightComponents, right); + left = resolveComposedSchema(leftComponents, left, new HashSet<>()); + right = resolveComposedSchema(rightComponents, right, new HashSet<>()); // If type of schemas are different, just set old & new schema, set changedType to true in // SchemaDiffResult and diff --git a/core/src/test/java/org/openapitools/openapidiff/core/AllOfOneOfDiffTest.java b/core/src/test/java/org/openapitools/openapidiff/core/AllOfOneOfDiffTest.java new file mode 100644 index 000000000..ec1bcfc53 --- /dev/null +++ b/core/src/test/java/org/openapitools/openapidiff/core/AllOfOneOfDiffTest.java @@ -0,0 +1,14 @@ +package org.openapitools.openapidiff.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.openapitools.openapidiff.core.model.ChangedOpenApi; + +public class AllOfOneOfDiffTest { + @Test + void allOfReferringToOneOfSchemasAreSupported() { + ChangedOpenApi diff = OpenApiCompare.fromLocations("issue-317_1.json", "issue-317_2.json"); + assertThat(diff.isCoreChanged().isUnchanged()); + } +} diff --git a/core/src/test/resources/issue-317_1.json b/core/src/test/resources/issue-317_1.json new file mode 100644 index 000000000..ab6f7357b --- /dev/null +++ b/core/src/test/resources/issue-317_1.json @@ -0,0 +1,84 @@ +{ + "openapi":"3.0.0", + "info":{ + "title":"API", + "version":"0.1.0" + }, + "paths":{ + "/resource":{ + "post":{ + "responses":{ + "200":{ + "description":"Created resource", + "content":{ + "application/json":{ + "schema":{ + "type":"string" + } + } + } + } + }, + "summary":"Create resource", + "requestBody":{ + "content":{ + "application/json":{ + "schema":{ + "$ref":"#/components/schemas/Resource" + } + } + }, + "description":"Definition of the resource" + } + } + } + }, + "components":{ + "schemas":{ + "Resource":{ + "type":"object", + "properties":{ + "assignment":{ + "$ref":"#/components/schemas/Foo" + } + } + }, + "Foo":{ + "oneOf":[ + { + "$ref":"#/components/schemas/Foo.Bar" + }, + { + "$ref":"#/components/schemas/Foo.Baz" + } + ], + "discriminator":{ + "propertyName":"type", + "mapping":{ + "Bar":"#/components/schemas/Foo.Bar", + "Baz":"#/components/schemas/Foo.Baz" + } + } + }, + "Foo.Bar":{ + "type":"object", + "properties":{ + "type":{ + "type":"string" + } + } + }, + "Foo.Baz":{ + "type":"object", + "properties":{ + "type":{ + "type":"string" + } + } + } + }, + "securitySchemes":{ + + } + } +} diff --git a/core/src/test/resources/issue-317_2.json b/core/src/test/resources/issue-317_2.json new file mode 100644 index 000000000..b3d35cde4 --- /dev/null +++ b/core/src/test/resources/issue-317_2.json @@ -0,0 +1,91 @@ +{ + "openapi":"3.0.0", + "info":{ + "title":"API", + "version":"0.1.0" + }, + "paths":{ + "/resource":{ + "post":{ + "responses":{ + "200":{ + "description":"Created resource", + "content":{ + "application/json":{ + "schema":{ + "type":"string" + } + } + } + } + }, + "summary":"Create resource", + "requestBody":{ + "content":{ + "application/json":{ + "schema":{ + "$ref":"#/components/schemas/Resource" + } + } + }, + "description":"Definition of the resource" + } + } + } + }, + "components":{ + "schemas":{ + "Resource":{ + "type":"object", + "properties":{ + "assignment":{ + "default":{ + "type":"Bar" + }, + "allOf":[ + { + "$ref":"#/components/schemas/Foo" + } + ] + } + } + }, + "Foo":{ + "oneOf":[ + { + "$ref":"#/components/schemas/Foo.Bar" + }, + { + "$ref":"#/components/schemas/Foo.Baz" + } + ], + "discriminator":{ + "propertyName":"type", + "mapping":{ + "Bar":"#/components/schemas/Foo.Bar", + "Baz":"#/components/schemas/Foo.Baz" + } + } + }, + "Foo.Bar":{ + "type":"object", + "properties":{ + "type":{ + "type":"string" + } + } + }, + "Foo.Baz":{ + "type":"object", + "properties":{ + "type":{ + "type":"string" + } + } + } + }, + "securitySchemes":{ + + } + } +}