Skip to content

Commit 9a787c5

Browse files
authored
Merge pull request #44 from quen2404/vendor-extensions
Vendor extensions support
2 parents a4709d4 + 6e156b6 commit 9a787c5

15 files changed

+152
-16
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ try {
129129
}
130130
```
131131

132+
### Extensions
133+
This project uses Java Service Provider Inteface (SPI) so additional extensions can be added.
134+
135+
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.
136+
132137
# Example
133138
### CLI Output
134139

src/main/java/com/qdesrame/openapi/diff/compare/ContentDiff.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public Optional<ChangedContent> diff(Content left, Content right, DiffContext co
3636
MediaType oldMediaType = left.get(mediaTypeKey);
3737
MediaType newMediaType = right.get(mediaTypeKey);
3838
ChangedMediaType changedMediaType = new ChangedMediaType(oldMediaType.getSchema(), newMediaType.getSchema(), context);
39-
openApiDiff.getSchemaDiff().diff(new HashSet<>(), oldMediaType.getSchema(), newMediaType.getSchema(), context).ifPresent(changedMediaType::setChangedSchema);
39+
openApiDiff.getSchemaDiff().diff(new HashSet<>(), oldMediaType.getSchema(), newMediaType.getSchema(), context.copyWithRequired(true)).ifPresent(changedMediaType::setChangedSchema);
4040
if (!isUnchanged(changedMediaType)) {
4141
changedMediaTypes.put(mediaTypeKey, changedMediaType);
4242
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.qdesrame.openapi.diff.compare;
2+
3+
import com.qdesrame.openapi.diff.model.Changed;
4+
import com.qdesrame.openapi.diff.model.DiffContext;
5+
6+
import java.util.Optional;
7+
8+
public interface ExtensionDiff {
9+
10+
ExtensionDiff setOpenApiDiff(OpenApiDiff openApiDiff);
11+
12+
String getName();
13+
14+
Optional<Changed> diff(Object left, Object right, DiffContext context);
15+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.qdesrame.openapi.diff.compare;
2+
3+
import com.qdesrame.openapi.diff.model.DiffContext;
4+
import com.qdesrame.openapi.diff.model.schema.ChangedExtensions;
5+
6+
import java.util.*;
7+
8+
import static com.qdesrame.openapi.diff.utils.ChangedUtils.isChanged;
9+
10+
public class ExtensionsDiff {
11+
private final OpenApiDiff openApiDiff;
12+
13+
private ServiceLoader<ExtensionDiff> extensionsLoader = ServiceLoader.load(ExtensionDiff.class);
14+
private List<ExtensionDiff> extensionsDiff = new ArrayList<>();
15+
16+
public ExtensionsDiff(OpenApiDiff openApiDiff) {
17+
this.openApiDiff = openApiDiff;
18+
this.extensionsLoader.reload();
19+
for (ExtensionDiff anExtensionsLoader : this.extensionsLoader) {
20+
extensionsDiff.add(anExtensionsLoader);
21+
}
22+
}
23+
24+
public Optional<ChangedExtensions> diff(Map<String, Object> left, Map<String, Object> right, DiffContext context) {
25+
if (null == left) left = new HashMap<>();
26+
if (null == right) right = new HashMap<>();
27+
ChangedExtensions changedExtensions = new ChangedExtensions(left, new HashMap<>(right), context);
28+
changedExtensions.getIncreased().putAll(right);
29+
for (String key : left.keySet()) {
30+
if (changedExtensions.getIncreased().containsKey(key)) {
31+
Optional<ExtensionDiff> extensionDiff = extensionsDiff.stream()
32+
.filter(diff -> ("x-" + diff.getName()).equals(key)).findFirst();
33+
Object leftValue = left.get(key);
34+
Object rightValue = changedExtensions.getIncreased().remove(key);
35+
extensionDiff.ifPresent(diff -> diff.setOpenApiDiff(openApiDiff).diff(leftValue, rightValue, context)
36+
.ifPresent(changed -> changedExtensions.getChanged().put(key, changed)));
37+
} else {
38+
changedExtensions.getMissing().put(key, left.get(key));
39+
}
40+
}
41+
return isChanged(changedExtensions);
42+
}
43+
}

src/main/java/com/qdesrame/openapi/diff/compare/HeaderDiff.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ protected Optional<ChangedHeader> computeDiff(HashSet<String> refSet, Header lef
4444
changedHeader.setChangeDeprecated(!Boolean.TRUE.equals(left.getDeprecated()) && Boolean.TRUE.equals(right.getDeprecated()));
4545
changedHeader.setChangeStyle(!Objects.equals(left.getStyle(), right.getStyle()));
4646
changedHeader.setChangeExplode(getBooleanDiff(left.getExplode(), right.getExplode()));
47-
openApiDiff.getSchemaDiff().diff(new HashSet<>(), left.getSchema(), right.getSchema(), context).ifPresent(changedHeader::setChangedSchema);
47+
openApiDiff.getSchemaDiff().diff(new HashSet<>(), left.getSchema(), right.getSchema(), context.copyWithRequired(true)).ifPresent(changedHeader::setChangedSchema);
4848
openApiDiff.getContentDiff().diff(left.getContent(), right.getContent(), context).ifPresent(changedHeader::setChangedContent);
4949

5050
return isChanged(changedHeader);

src/main/java/com/qdesrame/openapi/diff/compare/OpenApiDiff.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class OpenApiDiff {
4040
private SecuritySchemeDiff securitySchemeDiff;
4141
private OAuthFlowsDiff oAuthFlowsDiff;
4242
private OAuthFlowDiff oAuthFlowDiff;
43+
private ExtensionsDiff extensionsDiff;
4344

4445
private OpenAPI oldSpecOpenApi;
4546
private OpenAPI newSpecOpenApi;
@@ -84,6 +85,7 @@ private void initializeFields() {
8485
this.securitySchemeDiff = new SecuritySchemeDiff(this);
8586
this.oAuthFlowsDiff = new OAuthFlowsDiff(this);
8687
this.oAuthFlowDiff = new OAuthFlowDiff(this);
88+
this.extensionsDiff = new ExtensionsDiff(this);
8789
}
8890

8991
private ChangedOpenApi compare() {
@@ -201,6 +203,10 @@ public OAuthFlowDiff getoAuthFlowDiff() {
201203
return oAuthFlowDiff;
202204
}
203205

206+
public ExtensionsDiff getExtensionsDiff() {
207+
return extensionsDiff;
208+
}
209+
204210
public OpenAPI getOldSpecOpenApi() {
205211
return oldSpecOpenApi;
206212
}

src/main/java/com/qdesrame/openapi/diff/compare/ParameterDiff.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ protected Optional<ChangedParameter> computeDiff(HashSet<String> refSet, Paramet
4646
changedParameter.setChangeAllowEmptyValue(getBooleanDiff(left.getAllowEmptyValue(), right.getAllowEmptyValue()));
4747
changedParameter.setChangeStyle(!Objects.equals(left.getStyle(), right.getStyle()));
4848
changedParameter.setChangeExplode(getBooleanDiff(left.getExplode(), right.getExplode()));
49-
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context);
49+
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, left.getSchema(), right.getSchema(), context.copyWithRequired(true));
5050
if (changedSchema.isPresent()) {
5151
changedParameter.setChangedSchema(changedSchema.get());
5252
}

src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ArraySchemaDiffResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ public ArraySchemaDiffResult(OpenApiDiff openApiDiff) {
2222
public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftComponents, Components rightComponents, Schema left, Schema right, DiffContext context) {
2323
ArraySchema leftArraySchema = (ArraySchema) left;
2424
ArraySchema rightArraySchema = (ArraySchema) right;
25-
return openApiDiff.getSchemaDiff().diff(refSet, leftArraySchema.getItems(), rightArraySchema.getItems(), context);
25+
return openApiDiff.getSchemaDiff().diff(refSet, leftArraySchema.getItems(), rightArraySchema.getItems(), context.copyWithRequired(true));
2626
}
2727
}

src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/ComposedSchemaDiffResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftCompo
6666
leftSchema.set$ref(leftMapping.get(key));
6767
Schema rightSchema = new Schema();
6868
rightSchema.set$ref(rightMapping.get(key));
69-
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context);
69+
Optional<ChangedSchema> changedSchema = openApiDiff.getSchemaDiff().diff(refSet, leftSchema, rightSchema, context.copyWithRequired(true));
7070
changedSchema.ifPresent(schema -> changedMapping.put(key, schema));
7171
}
7272
changedSchema.setChangedOneOfSchema(changedOneOfSchema);

src/main/java/com/qdesrame/openapi/diff/compare/schemadiffresult/SchemaDiffResult.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55
import com.qdesrame.openapi.diff.model.ChangedSchema;
66
import com.qdesrame.openapi.diff.model.DiffContext;
77
import com.qdesrame.openapi.diff.model.ListDiff;
8+
import com.qdesrame.openapi.diff.model.schema.ChangedExtensions;
89
import com.qdesrame.openapi.diff.model.schema.ChangedReadOnly;
910
import com.qdesrame.openapi.diff.model.schema.ChangedWriteOnly;
1011
import io.swagger.v3.oas.models.Components;
1112
import io.swagger.v3.oas.models.media.Schema;
1213
import lombok.Getter;
1314

14-
import java.util.HashSet;
15-
import java.util.Map;
16-
import java.util.Objects;
17-
import java.util.Optional;
15+
import java.util.*;
1816

1917
import static com.qdesrame.openapi.diff.utils.ChangedUtils.isChanged;
2018

@@ -47,6 +45,8 @@ public Optional<ChangedSchema> diff(HashSet<String> refSet, Components leftCompo
4745
changedSchema.setChangedReadOnly(new ChangedReadOnly(context, left.getReadOnly(), right.getReadOnly()));
4846
changedSchema.setChangedWriteOnly(new ChangedWriteOnly(context, left.getWriteOnly(), right.getWriteOnly()));
4947
changedSchema.setChangedMaxLength(!Objects.equals(left.getMaxLength(), right.getMaxLength()));
48+
Optional<ChangedExtensions> changedExtensions = openApiDiff.getExtensionsDiff().diff(left.getExtensions(), right.getExtensions(), context);
49+
changedExtensions.ifPresent(changedSchema::setChangedExtensions);
5050

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

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

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

69+
private DiffContext required(DiffContext context, String key, List<String> required) {
70+
return context.copyWithRequired(required != null && required.contains(key));
71+
}
72+
6973
private void compareAdditionalProperties(HashSet<String> refSet, Schema leftSchema, Schema rightSchema, DiffContext context) {
7074
Object left = leftSchema.getAdditionalProperties();
7175
Object right = rightSchema.getAdditionalProperties();
@@ -78,7 +82,7 @@ private void compareAdditionalProperties(HashSet<String> refSet, Schema leftSche
7882
apChangedSchema.setNewSchema(rightAdditionalSchema);
7983
if (left != null && right != null) {
8084
Optional<ChangedSchema> addPropChangedSchemaOP
81-
= openApiDiff.getSchemaDiff().diff(refSet, leftAdditionalSchema, rightAdditionalSchema, context);
85+
= openApiDiff.getSchemaDiff().diff(refSet, leftAdditionalSchema, rightAdditionalSchema, context.copyWithRequired(false));
8286
apChangedSchema = addPropChangedSchemaOP.orElse(apChangedSchema);
8387
}
8488
isChanged(apChangedSchema).ifPresent(changedSchema::setAddPropChangedSchema);

src/main/java/com/qdesrame/openapi/diff/model/ChangedSchema.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.qdesrame.openapi.diff.model;
22

3+
import com.qdesrame.openapi.diff.model.schema.ChangedExtensions;
34
import com.qdesrame.openapi.diff.model.schema.ChangedReadOnly;
45
import com.qdesrame.openapi.diff.model.schema.ChangedWriteOnly;
56
import com.qdesrame.openapi.diff.utils.ChangedUtils;
@@ -38,6 +39,7 @@ public class ChangedSchema implements Changed {
3839
protected boolean discriminatorPropertyChanged;
3940
protected ChangedOneOfSchema changedOneOfSchema;
4041
protected ChangedSchema addPropChangedSchema;
42+
protected ChangedExtensions changedExtensions;
4143

4244
public ChangedSchema() {
4345
increasedProperties = new HashMap<>();
@@ -53,7 +55,8 @@ public DiffResult isChanged() {
5355
&& !changeFormat && increasedProperties.size() == 0 && missingProperties.size() == 0
5456
&& changedProperties.values().size() == 0 && !changeDeprecated
5557
&& (changeRequired == null || changeRequired.isUnchanged()) && !discriminatorPropertyChanged
56-
&& ChangedUtils.isUnchanged(addPropChangedSchema) && ChangedUtils.isUnchanged(changedOneOfSchema)) {
58+
&& ChangedUtils.isUnchanged(addPropChangedSchema) && ChangedUtils.isUnchanged(changedOneOfSchema)
59+
&& ChangedUtils.isUnchanged(changedExtensions)) {
5760
return DiffResult.NO_CHANGES;
5861
}
5962
boolean backwardCompatibleForRequest = (changeEnum == null || changeEnum.getMissing().isEmpty()) &&
@@ -72,7 +75,8 @@ public DiffResult isChanged() {
7275
if ((context.isRequest() && backwardCompatibleForRequest || context.isResponse() && backwardCompatibleForResponse)
7376
&& !changedType && !discriminatorPropertyChanged && ChangedUtils.isCompatible(changedOneOfSchema)
7477
&& ChangedUtils.isCompatible(addPropChangedSchema)
75-
&& changedProperties.values().stream().allMatch(Changed::isCompatible)) {
78+
&& changedProperties.values().stream().allMatch(Changed::isCompatible)
79+
&& ChangedUtils.isCompatible(changedExtensions)) {
7680
return DiffResult.COMPATIBLE;
7781
}
7882

src/main/java/com/qdesrame/openapi/diff/model/DiffContext.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class DiffContext {
1717
private PathItem.HttpMethod method;
1818
private boolean response;
1919
private boolean request;
20+
private Boolean required;
2021

2122
public DiffContext() {
2223
parameters = new HashMap<>();
@@ -28,6 +29,10 @@ public DiffContext copyWithMethod(PathItem.HttpMethod method) {
2829
return copy().setMethod(method);
2930
}
3031

32+
public DiffContext copyWithRequired(boolean required) {
33+
return copy().setRequired(required);
34+
}
35+
3136
public DiffContext copyAsRequest() {
3237
return copy().setRequest();
3338
}
@@ -81,6 +86,7 @@ private DiffContext copy() {
8186
context.method = this.method;
8287
context.response = this.response;
8388
context.request = this.request;
89+
context.required = this.required;
8490
return context;
8591
}
8692

@@ -93,6 +99,15 @@ public DiffContext setParameters(Map<String, String> parameters) {
9399
return this;
94100
}
95101

102+
public Boolean isRequired() {
103+
return required;
104+
}
105+
106+
private DiffContext setRequired(boolean required) {
107+
this.required = required;
108+
return this;
109+
}
110+
96111
@Override
97112
public boolean equals(Object o) {
98113
if (this == o) return true;
@@ -107,6 +122,7 @@ public boolean equals(Object o) {
107122
.append(url, that.url)
108123
.append(parameters, that.parameters)
109124
.append(method, that.method)
125+
.append(required, that.required)
110126
.isEquals();
111127
}
112128

@@ -118,6 +134,7 @@ public int hashCode() {
118134
.append(method)
119135
.append(response)
120136
.append(request)
137+
.append(required)
121138
.toHashCode();
122139
}
123140
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.qdesrame.openapi.diff.model.schema;
2+
3+
import com.qdesrame.openapi.diff.model.Changed;
4+
import com.qdesrame.openapi.diff.model.DiffContext;
5+
import com.qdesrame.openapi.diff.model.DiffResult;
6+
import lombok.Getter;
7+
import lombok.Setter;
8+
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
11+
12+
@Getter
13+
@Setter
14+
public class ChangedExtensions implements Changed {
15+
private final Map<String, Object> oldExtensions;
16+
private final Map<String, Object> newExtensions;
17+
private final DiffContext context;
18+
19+
private Map<String, Object> increased;
20+
private Map<String, Object> missing;
21+
private Map<String, Changed> changed;
22+
23+
public ChangedExtensions(Map<String, Object> oldExtensions, Map<String, Object> newExtensions, DiffContext context) {
24+
this.oldExtensions = oldExtensions;
25+
this.newExtensions = newExtensions;
26+
this.context = context;
27+
this.increased = new LinkedHashMap<>();
28+
this.missing = new LinkedHashMap<>();
29+
this.changed = new LinkedHashMap<>();
30+
}
31+
32+
@Override
33+
public DiffResult isChanged() {
34+
if (increased.isEmpty() && missing.isEmpty() && changed.isEmpty()) {
35+
return DiffResult.NO_CHANGES;
36+
}
37+
if (changed.values().stream().allMatch(Changed::isCompatible)) {
38+
return DiffResult.COMPATIBLE;
39+
}
40+
return DiffResult.INCOMPATIBLE;
41+
}
42+
}

src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedReadOnly.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public DiffResult isChanged() {
3030
}
3131
if (context.isRequest()) {
3232
if (Boolean.TRUE.equals(newValue)) {
33-
return DiffResult.COMPATIBLE;
34-
} else {
3533
return DiffResult.INCOMPATIBLE;
34+
} else {
35+
return context.isRequired() ? DiffResult.INCOMPATIBLE : DiffResult.COMPATIBLE;
3636
}
3737
}
3838
return DiffResult.UNKNOWN;

src/main/java/com/qdesrame/openapi/diff/model/schema/ChangedWriteOnly.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public DiffResult isChanged() {
3232
if (Boolean.TRUE.equals(newValue)) {
3333
return DiffResult.INCOMPATIBLE;
3434
} else {
35-
return DiffResult.COMPATIBLE;
35+
return context.isRequired() ? DiffResult.INCOMPATIBLE : DiffResult.COMPATIBLE;
3636
}
3737
}
3838
return DiffResult.UNKNOWN;

0 commit comments

Comments
 (0)