Skip to content

Commit dc6ad56

Browse files
authored
Configurable incompatibility checks (#552)
config-file and config-prop CLI options - Gives user fine-grained control of what constitutes an incompatibile change. Maven plugin config parameters - configFiles param for a separate yaml config file Corresponds to CLI --config-file option. - configProps param for specifying config props in pom.xml. Corresponds to CLI --config-prop option. - OpenApiDiffMojoTest updated to verify parameters work. Relies on recent exhaustive testing of incompatible checks to ensure no regressions (see #545). Fix #551 Fix #550 Fix #303
1 parent d4c9198 commit dc6ad56

File tree

76 files changed

+1074
-283
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1074
-283
lines changed

Diff for: README.md

+12
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ usage: openapi-diff <old> <new>
5151
compatible
5252
--fail-on-incompatible Fail only if API changes broke backward
5353
compatibility
54+
--config-file Config file to override default behavior. Supported file formats: .yaml
55+
--config-prop Config property to override default behavior with key:value format (e.g. my.prop:true)
5456
-h,--help print this message
5557
--header <property=value> use given header for authorisation
5658
--html <file> export diff as html in given file
@@ -119,6 +121,8 @@ usage: openapi-diff <old> <new>
119121
incompatible, compatible
120122
--fail-on-incompatible Fail only if API changes broke backward compatibility
121123
--fail-on-changed Fail if API changed but is backward compatible
124+
--config-file Config file to override default behavior. Supported file formats: .yaml
125+
--config-prop Config property to override default behavior with key:value format (e.g. my.prop:true)
122126
--trace be extra verbose
123127
--version print the version information and exit
124128
--warn Print warning information
@@ -153,6 +157,14 @@ Add openapi-diff to your POM to show diffs when you test your Maven project. You
153157
<jsonOutputFileName>${project.basedir}/../maven/target/diff.json</jsonOutputFileName>
154158
<!-- Supply file path for markdown output to file if desired. -->
155159
<markdownOutputFileName>${project.basedir}/../maven/target/diff.md</markdownOutputFileName>
160+
<!-- Supply config file(s), e.g. to disable incompatibility checks. Later files override earlier files -->
161+
<configFiles>
162+
<configFile>my/config-file.yaml</configFile>
163+
</configFiles>
164+
<!-- Supply config properties, e.g. to disable incompatibility checks. Overrides configFiles. -->
165+
<configProps>
166+
<incompatible.response.enum.increased>false</incompatible.response.enum.increased>
167+
</configProps>
156168
</configuration>
157169
</execution>
158170
</executions>

Diff for: cli/src/main/java/org/openapitools/openapidiff/cli/Main.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ch.qos.logback.classic.Level;
44
import io.swagger.v3.parser.core.models.AuthorizationValue;
55
import java.io.ByteArrayOutputStream;
6+
import java.io.File;
67
import java.io.FileOutputStream;
78
import java.io.OutputStreamWriter;
89
import java.util.Collections;
@@ -16,6 +17,7 @@
1617
import org.apache.commons.cli.ParseException;
1718
import org.apache.commons.lang3.exception.ExceptionUtils;
1819
import org.openapitools.openapidiff.core.OpenApiCompare;
20+
import org.openapitools.openapidiff.core.compare.OpenApiDiffOptions;
1921
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
2022
import org.openapitools.openapidiff.core.output.AsciidocRender;
2123
import org.openapitools.openapidiff.core.output.ConsoleRender;
@@ -49,6 +51,19 @@ public static void main(String... args) {
4951
.longOpt("fail-on-changed")
5052
.desc("Fail if API changed but is backward compatible")
5153
.build());
54+
options.addOption(
55+
Option.builder()
56+
.longOpt("config-file")
57+
.hasArg()
58+
.desc("Config file to override default behavior. Supported file formats: .yaml")
59+
.build());
60+
options.addOption(
61+
Option.builder()
62+
.longOpt("config-prop")
63+
.hasArg()
64+
.desc(
65+
"Config property to override default behavior. Arg in format of [propKey]:[propVal]")
66+
.build());
5267
options.addOption(Option.builder().longOpt("trace").desc("be extra verbose").build());
5368
options.addOption(
5469
Option.builder().longOpt("debug").desc("Print debugging information").build());
@@ -179,7 +194,27 @@ public static void main(String... args) {
179194
auths = Collections.singletonList(new AuthorizationValue(headers[0], headers[1], "header"));
180195
}
181196

182-
ChangedOpenApi result = OpenApiCompare.fromLocations(oldPath, newPath, auths);
197+
OpenApiDiffOptions.Builder optionBuilder = OpenApiDiffOptions.builder();
198+
String[] configFilePaths = line.getOptionValues("config-file");
199+
if (configFilePaths != null) {
200+
for (String configFilePath : configFilePaths) {
201+
optionBuilder.configYaml(new File(configFilePath));
202+
}
203+
}
204+
205+
String[] configProps = line.getOptionValues("config-prop");
206+
if (configProps != null) {
207+
for (String propKeyAndVal : configProps) {
208+
String[] split = propKeyAndVal.split(":");
209+
if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) {
210+
throw new IllegalArgumentException("--config-prop unexpected format: " + propKeyAndVal);
211+
}
212+
optionBuilder.configProperty(split[0], split[1]);
213+
}
214+
}
215+
OpenApiDiffOptions compareOpts = optionBuilder.build();
216+
217+
ChangedOpenApi result = OpenApiCompare.fromLocations(oldPath, newPath, auths, compareOpts);
183218
ConsoleRender consoleRender = new ConsoleRender();
184219
if (!logLevel.equals("OFF")) {
185220
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

Diff for: core/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
<groupId>org.apache.commons</groupId>
4141
<artifactId>commons-collections4</artifactId>
4242
</dependency>
43+
<dependency>
44+
<groupId>org.apache.commons</groupId>
45+
<artifactId>commons-configuration2</artifactId>
46+
</dependency>
4347
<dependency>
4448
<groupId>org.slf4j</groupId>
4549
<artifactId>slf4j-api</artifactId>

Diff for: core/src/main/java/org/openapitools/openapidiff/core/OpenApiCompare.java

+68-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.io.File;
88
import java.util.List;
99
import org.openapitools.openapidiff.core.compare.OpenApiDiff;
10+
import org.openapitools.openapidiff.core.compare.OpenApiDiffOptions;
1011
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
1112

1213
public class OpenApiCompare {
@@ -40,7 +41,25 @@ public static ChangedOpenApi fromContents(String oldContent, String newContent)
4041
*/
4142
public static ChangedOpenApi fromContents(
4243
String oldContent, String newContent, List<AuthorizationValue> auths) {
43-
return fromSpecifications(readContent(oldContent, auths), readContent(newContent, auths));
44+
return fromContents(oldContent, newContent, auths, OpenApiDiffOptions.builder().build());
45+
}
46+
47+
/**
48+
* compare two openapi doc
49+
*
50+
* @param oldContent old api-doc location:Json or Http
51+
* @param newContent new api-doc location:Json or Http
52+
* @param auths
53+
* @param options
54+
* @return Comparison result
55+
*/
56+
public static ChangedOpenApi fromContents(
57+
String oldContent,
58+
String newContent,
59+
List<AuthorizationValue> auths,
60+
OpenApiDiffOptions options) {
61+
return fromSpecifications(
62+
readContent(oldContent, auths), readContent(newContent, auths), options);
4463
}
4564

4665
/**
@@ -64,7 +83,21 @@ public static ChangedOpenApi fromFiles(File oldFile, File newFile) {
6483
*/
6584
public static ChangedOpenApi fromFiles(
6685
File oldFile, File newFile, List<AuthorizationValue> auths) {
67-
return fromLocations(oldFile.getAbsolutePath(), newFile.getAbsolutePath(), auths);
86+
return fromFiles(oldFile, newFile, auths, OpenApiDiffOptions.builder().build());
87+
}
88+
89+
/**
90+
* compare two openapi doc
91+
*
92+
* @param oldFile old api-doc file
93+
* @param newFile new api-doc file
94+
* @param auths
95+
* @param options
96+
* @return Comparison result
97+
*/
98+
public static ChangedOpenApi fromFiles(
99+
File oldFile, File newFile, List<AuthorizationValue> auths, OpenApiDiffOptions options) {
100+
return fromLocations(oldFile.getAbsolutePath(), newFile.getAbsolutePath(), auths, options);
68101
}
69102

70103
/**
@@ -88,7 +121,25 @@ public static ChangedOpenApi fromLocations(String oldLocation, String newLocatio
88121
*/
89122
public static ChangedOpenApi fromLocations(
90123
String oldLocation, String newLocation, List<AuthorizationValue> auths) {
91-
return fromSpecifications(readLocation(oldLocation, auths), readLocation(newLocation, auths));
124+
return fromLocations(oldLocation, newLocation, auths, OpenApiDiffOptions.builder().build());
125+
}
126+
127+
/**
128+
* compare two openapi doc
129+
*
130+
* @param oldLocation old api-doc location (local or http)
131+
* @param newLocation new api-doc location (local or http)
132+
* @param auths
133+
* @param options
134+
* @return Comparison result
135+
*/
136+
public static ChangedOpenApi fromLocations(
137+
String oldLocation,
138+
String newLocation,
139+
List<AuthorizationValue> auths,
140+
OpenApiDiffOptions options) {
141+
return fromSpecifications(
142+
readLocation(oldLocation, auths), readLocation(newLocation, auths), options);
92143
}
93144

94145
/**
@@ -99,7 +150,20 @@ public static ChangedOpenApi fromLocations(
99150
* @return Comparison result
100151
*/
101152
public static ChangedOpenApi fromSpecifications(OpenAPI oldSpec, OpenAPI newSpec) {
102-
return OpenApiDiff.compare(notNull(oldSpec, "old"), notNull(newSpec, "new"));
153+
return fromSpecifications(oldSpec, newSpec, OpenApiDiffOptions.builder().build());
154+
}
155+
156+
/**
157+
* compare two openapi doc
158+
*
159+
* @param oldSpec old api-doc specification
160+
* @param newSpec new api-doc specification
161+
* @param options
162+
* @return Comparison result
163+
*/
164+
public static ChangedOpenApi fromSpecifications(
165+
OpenAPI oldSpec, OpenAPI newSpec, OpenApiDiffOptions options) {
166+
return OpenApiDiff.compare(notNull(oldSpec, "old"), notNull(newSpec, "new"), options);
103167
}
104168

105169
private static OpenAPI notNull(OpenAPI spec, String type) {

Diff for: core/src/main/java/org/openapitools/openapidiff/core/compare/OAuthFlowDiff.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Objects;
99
import java.util.Optional;
1010
import org.openapitools.openapidiff.core.model.ChangedOAuthFlow;
11+
import org.openapitools.openapidiff.core.model.DiffContext;
1112

1213
public class OAuthFlowDiff {
1314
private final OpenApiDiff openApiDiff;
@@ -20,8 +21,8 @@ private static Map<String, Object> getExtensions(OAuthFlow oAuthFlow) {
2021
return ofNullable(oAuthFlow).map(OAuthFlow::getExtensions).orElse(null);
2122
}
2223

23-
public Optional<ChangedOAuthFlow> diff(OAuthFlow left, OAuthFlow right) {
24-
ChangedOAuthFlow changedOAuthFlow = new ChangedOAuthFlow(left, right);
24+
public Optional<ChangedOAuthFlow> diff(OAuthFlow left, OAuthFlow right, DiffContext context) {
25+
ChangedOAuthFlow changedOAuthFlow = new ChangedOAuthFlow(left, right, context);
2526
if (left != null && right != null) {
2627
changedOAuthFlow
2728
.setAuthorizationUrl(

Diff for: core/src/main/java/org/openapitools/openapidiff/core/compare/OAuthFlowsDiff.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Map;
88
import java.util.Optional;
99
import org.openapitools.openapidiff.core.model.ChangedOAuthFlows;
10+
import org.openapitools.openapidiff.core.model.DiffContext;
1011

1112
public class OAuthFlowsDiff {
1213
private final OpenApiDiff openApiDiff;
@@ -19,24 +20,24 @@ private static Map<String, Object> getExtensions(OAuthFlows oAuthFlow) {
1920
return ofNullable(oAuthFlow).map(OAuthFlows::getExtensions).orElse(null);
2021
}
2122

22-
public Optional<ChangedOAuthFlows> diff(OAuthFlows left, OAuthFlows right) {
23+
public Optional<ChangedOAuthFlows> diff(OAuthFlows left, OAuthFlows right, DiffContext context) {
2324
ChangedOAuthFlows changedOAuthFlows = new ChangedOAuthFlows(left, right);
2425
if (left != null && right != null) {
2526
openApiDiff
2627
.getOAuthFlowDiff()
27-
.diff(left.getImplicit(), right.getImplicit())
28+
.diff(left.getImplicit(), right.getImplicit(), context)
2829
.ifPresent(changedOAuthFlows::setImplicitOAuthFlow);
2930
openApiDiff
3031
.getOAuthFlowDiff()
31-
.diff(left.getPassword(), right.getPassword())
32+
.diff(left.getPassword(), right.getPassword(), context)
3233
.ifPresent(changedOAuthFlows::setPasswordOAuthFlow);
3334
openApiDiff
3435
.getOAuthFlowDiff()
35-
.diff(left.getClientCredentials(), right.getClientCredentials())
36+
.diff(left.getClientCredentials(), right.getClientCredentials(), context)
3637
.ifPresent(changedOAuthFlows::setClientCredentialOAuthFlow);
3738
openApiDiff
3839
.getOAuthFlowDiff()
39-
.diff(left.getAuthorizationCode(), right.getAuthorizationCode())
40+
.diff(left.getAuthorizationCode(), right.getAuthorizationCode(), context)
4041
.ifPresent(changedOAuthFlows::setAuthorizationCodeOAuthFlow);
4142
}
4243
openApiDiff

Diff for: core/src/main/java/org/openapitools/openapidiff/core/compare/OpenApiDiff.java

+15-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class OpenApiDiff {
4141
private MetadataDiff metadataDiff;
4242
private final OpenAPI oldSpecOpenApi;
4343
private final OpenAPI newSpecOpenApi;
44+
private final OpenApiDiffOptions options;
4445
private List<Endpoint> newEndpoints;
4546
private List<Endpoint> missingEndpoints;
4647
private List<ChangedOperation> changedOperations;
@@ -50,18 +51,24 @@ public class OpenApiDiff {
5051
/*
5152
* @param oldSpecOpenApi
5253
* @param newSpecOpenApi
54+
* @param diffOptions
5355
*/
54-
private OpenApiDiff(OpenAPI oldSpecOpenApi, OpenAPI newSpecOpenApi) {
56+
private OpenApiDiff(OpenAPI oldSpecOpenApi, OpenAPI newSpecOpenApi, OpenApiDiffOptions options) {
5557
this.oldSpecOpenApi = oldSpecOpenApi;
5658
this.newSpecOpenApi = newSpecOpenApi;
59+
this.options = options;
5760
if (null == oldSpecOpenApi || null == newSpecOpenApi) {
5861
throw new RuntimeException("one of the old or new object is null");
5962
}
63+
if (null == options) {
64+
throw new IllegalArgumentException("options parameter is null but is required");
65+
}
6066
initializeFields();
6167
}
6268

63-
public static ChangedOpenApi compare(OpenAPI oldSpec, OpenAPI newSpec) {
64-
return new OpenApiDiff(oldSpec, newSpec).compare();
69+
public static ChangedOpenApi compare(
70+
OpenAPI oldSpec, OpenAPI newSpec, OpenApiDiffOptions diffOptions) {
71+
return new OpenApiDiff(oldSpec, newSpec, diffOptions).compare();
6572
}
6673

6774
private void initializeFields() {
@@ -87,6 +94,10 @@ private void initializeFields() {
8794
this.deferredSchemaCache = new DeferredSchemaCache(this);
8895
}
8996

97+
public OpenApiDiffOptions getOptions() {
98+
return options;
99+
}
100+
90101
private ChangedOpenApi compare() {
91102
preProcess(oldSpecOpenApi);
92103
preProcess(newSpecOpenApi);
@@ -163,7 +174,7 @@ private void preProcess(OpenAPI openApi) {
163174
}
164175

165176
private ChangedOpenApi getChangedOpenApi() {
166-
return new ChangedOpenApi()
177+
return new ChangedOpenApi(options)
167178
.setMissingEndpoints(missingEndpoints)
168179
.setNewEndpoints(newEndpoints)
169180
.setNewSpecOpenApi(newSpecOpenApi)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.openapitools.openapidiff.core.compare;
2+
3+
import java.io.File;
4+
import java.io.FileNotFoundException;
5+
import java.io.FileReader;
6+
import org.apache.commons.configuration2.CompositeConfiguration;
7+
import org.apache.commons.configuration2.YAMLConfiguration;
8+
import org.apache.commons.configuration2.ex.ConfigurationException;
9+
10+
public class OpenApiDiffOptions {
11+
private final CompositeConfiguration config;
12+
13+
private OpenApiDiffOptions(CompositeConfiguration config) {
14+
this.config = config;
15+
}
16+
17+
public CompositeConfiguration getConfig() {
18+
return config;
19+
}
20+
21+
public static Builder builder() {
22+
return new Builder();
23+
}
24+
25+
public static class Builder {
26+
private OpenApiDiffOptions built = new OpenApiDiffOptions(new CompositeConfiguration());
27+
28+
public Builder configYaml(File file) {
29+
YAMLConfiguration yamlConfig = new YAMLConfiguration();
30+
try {
31+
yamlConfig.read(new FileReader(file));
32+
} catch (ConfigurationException | FileNotFoundException e) {
33+
throw new IllegalArgumentException("Problem loading config. file=" + file, e);
34+
}
35+
// Ideally immutable, but since it isn't, we just modify the config directly
36+
built.getConfig().addConfigurationFirst(yamlConfig);
37+
return this;
38+
}
39+
40+
public Builder configProperty(String propKey, String propVal) {
41+
built.getConfig().setProperty(propKey, propVal);
42+
return this;
43+
}
44+
45+
public OpenApiDiffOptions build() {
46+
return built;
47+
}
48+
}
49+
}

Diff for: core/src/main/java/org/openapitools/openapidiff/core/compare/PathsDiff.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public DeferredChanged<ChangedPaths> diff(
4040
final Map<String, PathItem> left, final Map<String, PathItem> right) {
4141
DeferredBuilder<Changed> builder = new DeferredBuilder<>();
4242

43-
ChangedPaths changedPaths = new ChangedPaths(left, right);
43+
ChangedPaths changedPaths = new ChangedPaths(left, right, openApiDiff.getOptions());
4444
changedPaths.getIncreased().putAll(right);
4545

4646
left.keySet()
@@ -82,7 +82,7 @@ public DeferredChanged<ChangedPaths> diff(
8282
params.put(oldParams.get(i), newParams.get(i));
8383
}
8484
}
85-
DiffContext context = new DiffContext();
85+
DiffContext context = new DiffContext(openApiDiff.getOptions());
8686
context.setUrl(url);
8787
context.setParameters(params);
8888
context.setLeftAndRightUrls(url, rightUrl);

0 commit comments

Comments
 (0)