From 258c7d4dc867dfa7f77a1db49f39d5971dbcf29e Mon Sep 17 00:00:00 2001 From: Quentin Desrame Date: Thu, 26 Apr 2018 16:06:18 +0200 Subject: [PATCH 1/2] Vendor extensions support --- README.md | 5 +++ .../openapi/diff/compare/ExtensionDiff.java | 15 +++++++ .../openapi/diff/compare/ExtensionsDiff.java | 43 +++++++++++++++++++ .../openapi/diff/compare/OpenApiDiff.java | 6 +++ .../schemadiffresult/SchemaDiffResult.java | 3 ++ .../openapi/diff/model/ChangedSchema.java | 8 +++- .../diff/model/schema/ChangedExtensions.java | 42 ++++++++++++++++++ 7 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/qdesrame/openapi/diff/compare/ExtensionDiff.java create mode 100644 src/main/java/com/qdesrame/openapi/diff/compare/ExtensionsDiff.java create mode 100644 src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedExtensions.java diff --git a/README.md b/README.md index ae0a74ab5..54b9dbd4f 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ try { } ``` +### Extensions +This project uses Java Service Provider Inteface (SPI) so additional extensions can be added. + +To build your own extension, you simply need to create a `src/main/resources/META-INF/services/com.qdesrame.openapi.diff.compare.ExtensionDiff` file with the full classname of your implementation. Your class must also implement the `com.qdesrame.openapi.diff.compare.ExtensionDiff` interface. Then, including your library with the `openapi-diff` module will cause it to be triggered automatically. + # Example ### CLI Output diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionDiff.java new file mode 100644 index 000000000..7c2c7005f --- /dev/null +++ b/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionDiff.java @@ -0,0 +1,15 @@ +package com.qdesrame.openapi.diff.compare; + +import com.qdesrame.openapi.diff.model.Changed; +import com.qdesrame.openapi.diff.model.DiffContext; + +import java.util.Optional; + +public interface ExtensionDiff { + + ExtensionDiff setOpenApiDiff(OpenApiDiff openApiDiff); + + String getName(); + + Optional diff(Object left, Object right, DiffContext context); +} diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionsDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionsDiff.java new file mode 100644 index 000000000..3fef0c584 --- /dev/null +++ b/src/main/java/com/qdesrame/openapi/diff/compare/ExtensionsDiff.java @@ -0,0 +1,43 @@ +package com.qdesrame.openapi.diff.compare; + +import com.qdesrame.openapi.diff.model.DiffContext; +import com.qdesrame.openapi.diff.model.schema.ChangedExtensions; + +import java.util.*; + +import static com.qdesrame.openapi.diff.utils.ChangedUtils.isChanged; + +public class ExtensionsDiff { + private final OpenApiDiff openApiDiff; + + private ServiceLoader extensionsLoader = ServiceLoader.load(ExtensionDiff.class); + private List extensionsDiff = new ArrayList<>(); + + public ExtensionsDiff(OpenApiDiff openApiDiff) { + this.openApiDiff = openApiDiff; + this.extensionsLoader.reload(); + for (ExtensionDiff anExtensionsLoader : this.extensionsLoader) { + extensionsDiff.add(anExtensionsLoader); + } + } + + public Optional diff(Map left, Map right, DiffContext context) { + if (null == left) left = new HashMap<>(); + if (null == right) right = new HashMap<>(); + ChangedExtensions changedExtensions = new ChangedExtensions(left, new HashMap<>(right), context); + changedExtensions.getIncreased().putAll(right); + for (String key : left.keySet()) { + if (changedExtensions.getIncreased().containsKey(key)) { + Optional extensionDiff = extensionsDiff.stream() + .filter(diff -> ("x-" + diff.getName()).equals(key)).findFirst(); + Object leftValue = left.get(key); + Object rightValue = changedExtensions.getIncreased().remove(key); + extensionDiff.ifPresent(diff -> diff.setOpenApiDiff(openApiDiff).diff(leftValue, rightValue, context) + .ifPresent(changed -> changedExtensions.getChanged().put(key, changed))); + } else { + changedExtensions.getMissing().put(key, left.get(key)); + } + } + return isChanged(changedExtensions); + } +} diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/OpenApiDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/OpenApiDiff.java index ac246d426..06275124f 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/OpenApiDiff.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/OpenApiDiff.java @@ -40,6 +40,7 @@ public class OpenApiDiff { private SecuritySchemeDiff securitySchemeDiff; private OAuthFlowsDiff oAuthFlowsDiff; private OAuthFlowDiff oAuthFlowDiff; + private ExtensionsDiff extensionsDiff; private OpenAPI oldSpecOpenApi; private OpenAPI newSpecOpenApi; @@ -84,6 +85,7 @@ private void initializeFields() { this.securitySchemeDiff = new SecuritySchemeDiff(this); this.oAuthFlowsDiff = new OAuthFlowsDiff(this); this.oAuthFlowDiff = new OAuthFlowDiff(this); + this.extensionsDiff = new ExtensionsDiff(this); } private ChangedOpenApi compare() { @@ -201,6 +203,10 @@ public OAuthFlowDiff getoAuthFlowDiff() { return oAuthFlowDiff; } + public ExtensionsDiff getExtensionsDiff() { + return extensionsDiff; + } + public OpenAPI getOldSpecOpenApi() { return oldSpecOpenApi; } diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java index 3d6e3acc7..d5e174664 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java @@ -5,6 +5,7 @@ import com.qdesrame.openapi.diff.model.ChangedSchema; import com.qdesrame.openapi.diff.model.DiffContext; import com.qdesrame.openapi.diff.model.ListDiff; +import com.qdesrame.openapi.diff.model.schema.ChangedExtensions; import com.qdesrame.openapi.diff.model.schema.ChangedReadOnly; import com.qdesrame.openapi.diff.model.schema.ChangedWriteOnly; import io.swagger.v3.oas.models.Components; @@ -47,6 +48,8 @@ public Optional diff(HashSet refSet, Components leftCompo changedSchema.setChangedReadOnly(new ChangedReadOnly(context, left.getReadOnly(), right.getReadOnly())); changedSchema.setChangedWriteOnly(new ChangedWriteOnly(context, left.getWriteOnly(), right.getWriteOnly())); changedSchema.setChangedMaxLength(!Objects.equals(left.getMaxLength(), right.getMaxLength())); + Optional changedExtensions = openApiDiff.getExtensionsDiff().diff(left.getExtensions(), right.getExtensions(), context); + changedExtensions.ifPresent(changedSchema::setChangedExtensions); Map leftProperties = null == left ? null : left.getProperties(); Map rightProperties = null == right ? null : right.getProperties(); diff --git a/src/main/java/com/qdesrame/openapi/diff/model/ChangedSchema.java b/src/main/java/com/qdesrame/openapi/diff/model/ChangedSchema.java index b7c079564..1d17d2072 100644 --- a/src/main/java/com/qdesrame/openapi/diff/model/ChangedSchema.java +++ b/src/main/java/com/qdesrame/openapi/diff/model/ChangedSchema.java @@ -1,5 +1,6 @@ package com.qdesrame.openapi.diff.model; +import com.qdesrame.openapi.diff.model.schema.ChangedExtensions; import com.qdesrame.openapi.diff.model.schema.ChangedReadOnly; import com.qdesrame.openapi.diff.model.schema.ChangedWriteOnly; import com.qdesrame.openapi.diff.utils.ChangedUtils; @@ -38,6 +39,7 @@ public class ChangedSchema implements Changed { protected boolean discriminatorPropertyChanged; protected ChangedOneOfSchema changedOneOfSchema; protected ChangedSchema addPropChangedSchema; + protected ChangedExtensions changedExtensions; public ChangedSchema() { increasedProperties = new HashMap<>(); @@ -53,7 +55,8 @@ public DiffResult isChanged() { && !changeFormat && increasedProperties.size() == 0 && missingProperties.size() == 0 && changedProperties.values().size() == 0 && !changeDeprecated && (changeRequired == null || changeRequired.isUnchanged()) && !discriminatorPropertyChanged - && ChangedUtils.isUnchanged(addPropChangedSchema) && ChangedUtils.isUnchanged(changedOneOfSchema)) { + && ChangedUtils.isUnchanged(addPropChangedSchema) && ChangedUtils.isUnchanged(changedOneOfSchema) + && ChangedUtils.isUnchanged(changedExtensions)) { return DiffResult.NO_CHANGES; } boolean backwardCompatibleForRequest = (changeEnum == null || changeEnum.getMissing().isEmpty()) && @@ -71,7 +74,8 @@ public DiffResult isChanged() { if ((context.isRequest() && backwardCompatibleForRequest || context.isResponse() && backwardCompatibleForResponse) && !changedType && !discriminatorPropertyChanged && ChangedUtils.isCompatible(changedOneOfSchema) && ChangedUtils.isCompatible(addPropChangedSchema) - && changedProperties.values().stream().allMatch(Changed::isCompatible)) { + && changedProperties.values().stream().allMatch(Changed::isCompatible) + && ChangedUtils.isCompatible(changedExtensions)) { return DiffResult.COMPATIBLE; } diff --git a/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedExtensions.java b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedExtensions.java new file mode 100644 index 000000000..94174c690 --- /dev/null +++ b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedExtensions.java @@ -0,0 +1,42 @@ +package com.qdesrame.openapi.diff.model.schema; + +import com.qdesrame.openapi.diff.model.Changed; +import com.qdesrame.openapi.diff.model.DiffContext; +import com.qdesrame.openapi.diff.model.DiffResult; +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Getter +@Setter +public class ChangedExtensions implements Changed { + private final Map oldExtensions; + private final Map newExtensions; + private final DiffContext context; + + private Map increased; + private Map missing; + private Map changed; + + public ChangedExtensions(Map oldExtensions, Map newExtensions, DiffContext context) { + this.oldExtensions = oldExtensions; + this.newExtensions = newExtensions; + this.context = context; + this.increased = new LinkedHashMap<>(); + this.missing = new LinkedHashMap<>(); + this.changed = new LinkedHashMap<>(); + } + + @Override + public DiffResult isChanged() { + if (increased.isEmpty() && missing.isEmpty() && changed.isEmpty()) { + return DiffResult.NO_CHANGES; + } + if (changed.values().stream().allMatch(Changed::isCompatible)) { + return DiffResult.COMPATIBLE; + } + return DiffResult.INCOMPATIBLE; + } +} From 6e156b601294f411de388e9dd2e2a547e156de26 Mon Sep 17 00:00:00 2001 From: Quentin Desrame Date: Wed, 2 May 2018 14:14:36 +0200 Subject: [PATCH 2/2] Add required info in context --- .../openapi/diff/compare/ContentDiff.java | 2 +- .../openapi/diff/compare/HeaderDiff.java | 2 +- .../openapi/diff/compare/ParameterDiff.java | 2 +- .../schemadiffresult/ArraySchemaDiffResult.java | 2 +- .../ComposedSchemaDiffResult.java | 2 +- .../schemadiffresult/SchemaDiffResult.java | 13 +++++++------ .../openapi/diff/model/DiffContext.java | 17 +++++++++++++++++ .../diff/model/schema/ChangedReadOnly.java | 4 ++-- .../diff/model/schema/ChangedWriteOnly.java | 2 +- 9 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/ContentDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/ContentDiff.java index 145118065..6b138ff27 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/ContentDiff.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/ContentDiff.java @@ -36,7 +36,7 @@ public Optional diff(Content left, Content right, DiffContext co MediaType oldMediaType = left.get(mediaTypeKey); MediaType newMediaType = right.get(mediaTypeKey); ChangedMediaType changedMediaType = new ChangedMediaType(oldMediaType.getSchema(), newMediaType.getSchema(), context); - openApiDiff.getSchemaDiff().diff(new HashSet<>(), oldMediaType.getSchema(), newMediaType.getSchema(), context).ifPresent(changedMediaType::setChangedSchema); + openApiDiff.getSchemaDiff().diff(new HashSet<>(), oldMediaType.getSchema(), newMediaType.getSchema(), context.copyWithRequired(true)).ifPresent(changedMediaType::setChangedSchema); if (!isUnchanged(changedMediaType)) { changedMediaTypes.put(mediaTypeKey, changedMediaType); } diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/HeaderDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/HeaderDiff.java index b2a6c6dbd..3bd8fb991 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/HeaderDiff.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/HeaderDiff.java @@ -44,7 +44,7 @@ protected Optional computeDiff(HashSet refSet, Header lef changedHeader.setChangeDeprecated(!Boolean.TRUE.equals(left.getDeprecated()) && Boolean.TRUE.equals(right.getDeprecated())); changedHeader.setChangeStyle(!Objects.equals(left.getStyle(), right.getStyle())); changedHeader.setChangeExplode(getBooleanDiff(left.getExplode(), right.getExplode())); - openApiDiff.getSchemaDiff().diff(new HashSet<>(), left.getSchema(), right.getSchema(), context).ifPresent(changedHeader::setChangedSchema); + openApiDiff.getSchemaDiff().diff(new HashSet<>(), left.getSchema(), right.getSchema(), context.copyWithRequired(true)).ifPresent(changedHeader::setChangedSchema); openApiDiff.getContentDiff().diff(left.getContent(), right.getContent(), context).ifPresent(changedHeader::setChangedContent); return isChanged(changedHeader); diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/ParameterDiff.java b/src/main/java/com/qdesrame/openapi/diff/compare/ParameterDiff.java index 25bdedb69..82e2e316c 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/ParameterDiff.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/ParameterDiff.java @@ -46,7 +46,7 @@ protected Optional computeDiff(HashSet refSet, Paramet changedParameter.setChangeAllowEmptyValue(getBooleanDiff(left.getAllowEmptyValue(), right.getAllowEmptyValue())); changedParameter.setChangeStyle(!Objects.equals(left.getStyle(), right.getStyle())); changedParameter.setChangeExplode(getBooleanDiff(left.getExplode(), right.getExplode())); - Optional changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context); + Optional changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context.copyWithRequired(true)); if (changedSchema.isPresent()) { changedParameter.setChangedSchema(changedSchema.get()); } diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ArraySchemaDiffResult.java b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ArraySchemaDiffResult.java index 02a7deec8..fa57cb962 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ArraySchemaDiffResult.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ArraySchemaDiffResult.java @@ -22,6 +22,6 @@ public ArraySchemaDiffResult(OpenApiDiff openApiDiff) { public Optional diff(HashSet refSet, Components leftComponents, Components rightComponents, Schema left, Schema right, DiffContext context) { ArraySchema leftArraySchema = (ArraySchema) left; ArraySchema rightArraySchema = (ArraySchema) right; - return openApiDiff.getSchemaDiff().diff(refSet, leftArraySchema.getItems(), rightArraySchema.getItems(), context); + return openApiDiff.getSchemaDiff().diff(refSet, leftArraySchema.getItems(), rightArraySchema.getItems(), context.copyWithRequired(true)); } } diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ComposedSchemaDiffResult.java b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ComposedSchemaDiffResult.java index 929ef15d6..ebf61d5a5 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ComposedSchemaDiffResult.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ComposedSchemaDiffResult.java @@ -66,7 +66,7 @@ public Optional diff(HashSet refSet, Components leftCompo leftSchema.set$ref(leftMapping.get(key)); Schema rightSchema = new Schema(); rightSchema.set$ref(rightMapping.get(key)); - Optional changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context); + Optional changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context.copyWithRequired(true)); changedSchema.ifPresent(schema -> changedMapping.put(key, schema)); } changedSchema.setChangedOneOfSchema(changedOneOfSchema); diff --git a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java index d5e174664..fcd3b0324 100644 --- a/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java +++ b/src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java @@ -12,10 +12,7 @@ import io.swagger.v3.oas.models.media.Schema; import lombok.Getter; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import static com.qdesrame.openapi.diff.utils.ChangedUtils.isChanged; @@ -58,7 +55,7 @@ public Optional diff(HashSet refSet, Components leftCompo Map missingProp = propertyDiff.getMissing(); for (String key : propertyDiff.getSharedKey()) { - Optional resultSchema = openApiDiff.getSchemaDiff().diff(refSet, leftProperties.get(key), rightProperties.get(key), context); + Optional resultSchema = openApiDiff.getSchemaDiff().diff(refSet, leftProperties.get(key), rightProperties.get(key), required(context, key, right.getRequired())); resultSchema.ifPresent(changedSchema1 -> changedSchema.getChangedProperties().put(key, changedSchema1)); } @@ -69,6 +66,10 @@ public Optional diff(HashSet refSet, Components leftCompo return isChanged(changedSchema); } + private DiffContext required(DiffContext context, String key, List required) { + return context.copyWithRequired(required != null && required.contains(key)); + } + private void compareAdditionalProperties(HashSet refSet, Schema leftSchema, Schema rightSchema, DiffContext context) { Object left = leftSchema.getAdditionalProperties(); Object right = rightSchema.getAdditionalProperties(); @@ -81,7 +82,7 @@ private void compareAdditionalProperties(HashSet refSet, Schema leftSche apChangedSchema.setNewSchema(rightAdditionalSchema); if (left != null && right != null) { Optional addPropChangedSchemaOP - = openApiDiff.getSchemaDiff().diff(refSet, leftAdditionalSchema, rightAdditionalSchema, context); + = openApiDiff.getSchemaDiff().diff(refSet, leftAdditionalSchema, rightAdditionalSchema, context.copyWithRequired(false)); apChangedSchema = addPropChangedSchemaOP.orElse(apChangedSchema); } isChanged(apChangedSchema).ifPresent(changedSchema::setAddPropChangedSchema); diff --git a/src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java b/src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java index 12392a6e5..9fe1f6d8d 100644 --- a/src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java +++ b/src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java @@ -17,6 +17,7 @@ public class DiffContext { private PathItem.HttpMethod method; private boolean response; private boolean request; + private Boolean required; public DiffContext() { parameters = new HashMap<>(); @@ -28,6 +29,10 @@ public DiffContext copyWithMethod(PathItem.HttpMethod method) { return copy().setMethod(method); } + public DiffContext copyWithRequired(boolean required) { + return copy().setRequired(required); + } + public DiffContext copyAsRequest() { return copy().setRequest(); } @@ -81,6 +86,7 @@ private DiffContext copy() { context.method = this.method; context.response = this.response; context.request = this.request; + context.required = this.required; return context; } @@ -93,6 +99,15 @@ public DiffContext setParameters(Map parameters) { return this; } + public Boolean isRequired() { + return required; + } + + private DiffContext setRequired(boolean required) { + this.required = required; + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -107,6 +122,7 @@ public boolean equals(Object o) { .append(url, that.url) .append(parameters, that.parameters) .append(method, that.method) + .append(required, that.required) .isEquals(); } @@ -118,6 +134,7 @@ public int hashCode() { .append(method) .append(response) .append(request) + .append(required) .toHashCode(); } } diff --git a/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedReadOnly.java b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedReadOnly.java index e01c1f6de..838da144f 100644 --- a/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedReadOnly.java +++ b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedReadOnly.java @@ -30,9 +30,9 @@ public DiffResult isChanged() { } if (context.isRequest()) { if (Boolean.TRUE.equals(newValue)) { - return DiffResult.COMPATIBLE; - } else { return DiffResult.INCOMPATIBLE; + } else { + return context.isRequired() ? DiffResult.INCOMPATIBLE : DiffResult.COMPATIBLE; } } return DiffResult.UNKNOWN; diff --git a/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedWriteOnly.java b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedWriteOnly.java index 148b84852..068b47f69 100644 --- a/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedWriteOnly.java +++ b/src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedWriteOnly.java @@ -32,7 +32,7 @@ public DiffResult isChanged() { if (Boolean.TRUE.equals(newValue)) { return DiffResult.INCOMPATIBLE; } else { - return DiffResult.COMPATIBLE; + return context.isRequired() ? DiffResult.INCOMPATIBLE : DiffResult.COMPATIBLE; } } return DiffResult.UNKNOWN;