Skip to content

Vendor extensions support #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public Optional<ChangedContent> 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);
}
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/qdesrame/openapi/diff/compare/ExtensionDiff.java
Original file line number Diff line number Diff line change
@@ -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<Changed> diff(Object left, Object right, DiffContext context);
}
Original file line number Diff line number Diff line change
@@ -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<ExtensionDiff> extensionsLoader = ServiceLoader.load(ExtensionDiff.class);
private List<ExtensionDiff> extensionsDiff = new ArrayList<>();

public ExtensionsDiff(OpenApiDiff openApiDiff) {
this.openApiDiff = openApiDiff;
this.extensionsLoader.reload();
for (ExtensionDiff anExtensionsLoader : this.extensionsLoader) {
extensionsDiff.add(anExtensionsLoader);
}
}

public Optional<ChangedExtensions> diff(Map<String, Object> left, Map<String, Object> 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> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected Optional<ChangedHeader> computeDiff(HashSet<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -201,6 +203,10 @@ public OAuthFlowDiff getoAuthFlowDiff() {
return oAuthFlowDiff;
}

public ExtensionsDiff getExtensionsDiff() {
return extensionsDiff;
}

public OpenAPI getOldSpecOpenApi() {
return oldSpecOpenApi;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected Optional<ChangedParameter> computeDiff(HashSet<String> 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> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context);
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context.copyWithRequired(true));
if (changedSchema.isPresent()) {
changedParameter.setChangedSchema(changedSchema.get());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public ArraySchemaDiffResult(OpenApiDiff openApiDiff) {
public Optional<ChangedSchema> diff(HashSet<String> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftCompo
leftSchema.set$ref(leftMapping.get(key));
Schema rightSchema = new Schema();
rightSchema.set$ref(rightMapping.get(key));
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context);
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context.copyWithRequired(true));
changedSchema.ifPresent(schema -> changedMapping.put(key, schema));
}
changedSchema.setChangedOneOfSchema(changedOneOfSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
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;
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;

Expand Down Expand Up @@ -47,6 +45,8 @@ public Optional<ChangedSchema> diff(HashSet<String> 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> changedExtensions = openApiDiff.getExtensionsDiff().diff(left.getExtensions(), right.getExtensions(), context);
changedExtensions.ifPresent(changedSchema::setChangedExtensions);

Map<String, Schema> leftProperties = null == left ? null : left.getProperties();
Map<String, Schema> rightProperties = null == right ? null : right.getProperties();
Expand All @@ -55,7 +55,7 @@ public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftCompo
Map<String, Schema> missingProp = propertyDiff.getMissing();

for (String key : propertyDiff.getSharedKey()) {
Optional<ChangedSchema> resultSchema = openApiDiff.getSchemaDiff().diff(refSet, leftProperties.get(key), rightProperties.get(key), context);
Optional<ChangedSchema> resultSchema = openApiDiff.getSchemaDiff().diff(refSet, leftProperties.get(key), rightProperties.get(key), required(context, key, right.getRequired()));
resultSchema.ifPresent(changedSchema1 -> changedSchema.getChangedProperties().put(key, changedSchema1));
}

Expand All @@ -66,6 +66,10 @@ public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftCompo
return isChanged(changedSchema);
}

private DiffContext required(DiffContext context, String key, List<String> required) {
return context.copyWithRequired(required != null && required.contains(key));
}

private void compareAdditionalProperties(HashSet<String> refSet, Schema leftSchema, Schema rightSchema, DiffContext context) {
Object left = leftSchema.getAdditionalProperties();
Object right = rightSchema.getAdditionalProperties();
Expand All @@ -78,7 +82,7 @@ private void compareAdditionalProperties(HashSet<String> refSet, Schema leftSche
apChangedSchema.setNewSchema(rightAdditionalSchema);
if (left != null && right != null) {
Optional<ChangedSchema> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<>();
Expand All @@ -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()) &&
Expand All @@ -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;
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<>();
Expand All @@ -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();
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -93,6 +99,15 @@ public DiffContext setParameters(Map<String, String> 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;
Expand All @@ -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();
}

Expand All @@ -118,6 +134,7 @@ public int hashCode() {
.append(method)
.append(response)
.append(request)
.append(required)
.toHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> oldExtensions;
private final Map<String, Object> newExtensions;
private final DiffContext context;

private Map<String, Object> increased;
private Map<String, Object> missing;
private Map<String, Changed> changed;

public ChangedExtensions(Map<String, Object> oldExtensions, Map<String, Object> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down