Skip to content
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

Configurable incompatibility checks #552

Merged
merged 27 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f093580
config-file and config-prop CLI options
westse Jun 27, 2023
19d7901
Maven plugin config parameters
westse Jul 26, 2023
d1b0245
Configurable incompatible checks - Enum
westse Jul 18, 2023
a19683a
Configurable incompatible checks - MaxLength
westse Jul 18, 2023
3f43dc5
Configurable incompatible checks - Required
westse Jul 18, 2023
ec14f58
Configurable incompatible checks - ApiResponse
westse Jul 18, 2023
d3702b8
Configurable incompatible checks - Content
westse Jul 18, 2023
e53eb32
Configurable incompatible checks - Schema
westse Jul 19, 2023
acac9d2
Configurable incompatible checks - Extensions
westse Jul 19, 2023
290a489
Fix NumericRange compatibility checks
westse Jul 19, 2023
2111467
Configurable incompatible checks - NumericRange
westse Jul 20, 2023
fac7577
Configurable incompatible checks - ReadOnly
westse Jul 20, 2023
38340b6
Configurable incompatible checks - WriteOnly
westse Jul 20, 2023
8ed4e54
Configurable incompatible checks - Headers
westse Jul 20, 2023
e0609bb
Configurable incompatible checks - Header
westse Jul 20, 2023
ac3981c
Configurable incompatible checks - OneOf
westse Jul 20, 2023
839cb04
Configurable incompatible checks - OpenApi
westse Jul 21, 2023
2af9762
Configurable incompatible checks - Parameters
westse Jul 21, 2023
a38b6d4
Configurable incompatible checks - Parameter
westse Jul 21, 2023
d5b6104
Configurable incompatible checks - Paths
westse Jul 21, 2023
8c19195
Configurable incompatible checks - Path
westse Jul 25, 2023
9b28c66
Configurable incompatible checks - RequestBody
westse Jul 25, 2023
4ff68fa
Configurable incompatible checks - SecurityScheme
westse Jul 25, 2023
c51495f
Configurable incompatible checks - SecurityRequirement
westse Jul 25, 2023
60c3f1a
Configurable incompatible checks - SecurityRequirements
westse Jul 25, 2023
bec808b
Configurable incompatible checks - OauthFlow
westse Jul 26, 2023
058058c
Merge branch 'master' into configure-check-compatible
joschi Dec 16, 2023
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ usage: openapi-diff <old> <new>
compatible
--fail-on-incompatible Fail only if API changes broke backward
compatibility
--config-file Config file to override default behavior. Supported file formats: .yaml
--config-prop Config property to override default behavior with key:value format (e.g. my.prop:true)
-h,--help print this message
--header <property=value> use given header for authorisation
--html <file> export diff as html in given file
Expand Down Expand Up @@ -119,6 +121,8 @@ usage: openapi-diff <old> <new>
incompatible, compatible
--fail-on-incompatible Fail only if API changes broke backward compatibility
--fail-on-changed Fail if API changed but is backward compatible
--config-file Config file to override default behavior. Supported file formats: .yaml
--config-prop Config property to override default behavior with key:value format (e.g. my.prop:true)
--trace be extra verbose
--version print the version information and exit
--warn Print warning information
Expand Down Expand Up @@ -153,6 +157,14 @@ Add openapi-diff to your POM to show diffs when you test your Maven project. You
<jsonOutputFileName>${project.basedir}/../maven/target/diff.json</jsonOutputFileName>
<!-- Supply file path for markdown output to file if desired. -->
<markdownOutputFileName>${project.basedir}/../maven/target/diff.md</markdownOutputFileName>
<!-- Supply config file(s), e.g. to disable incompatibility checks. Later files override earlier files -->
<configFiles>
<configFile>my/config-file.yaml</configFile>
</configFiles>
<!-- Supply config properties, e.g. to disable incompatibility checks. Overrides configFiles. -->
<configProps>
<incompatible.response.enum.increased>false</incompatible.response.enum.increased>
</configProps>
</configuration>
</execution>
</executions>
Expand Down
37 changes: 36 additions & 1 deletion cli/src/main/java/org/openapitools/openapidiff/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ch.qos.logback.classic.Level;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Collections;
Expand All @@ -16,6 +17,7 @@
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.openapitools.openapidiff.core.OpenApiCompare;
import org.openapitools.openapidiff.core.compare.OpenApiDiffOptions;
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
import org.openapitools.openapidiff.core.output.AsciidocRender;
import org.openapitools.openapidiff.core.output.ConsoleRender;
Expand Down Expand Up @@ -49,6 +51,19 @@ public static void main(String... args) {
.longOpt("fail-on-changed")
.desc("Fail if API changed but is backward compatible")
.build());
options.addOption(
Option.builder()
.longOpt("config-file")
.hasArg()
.desc("Config file to override default behavior. Supported file formats: .yaml")
.build());
options.addOption(
Option.builder()
.longOpt("config-prop")
.hasArg()
.desc(
"Config property to override default behavior. Arg in format of [propKey]:[propVal]")
.build());
options.addOption(Option.builder().longOpt("trace").desc("be extra verbose").build());
options.addOption(
Option.builder().longOpt("debug").desc("Print debugging information").build());
Expand Down Expand Up @@ -179,7 +194,27 @@ public static void main(String... args) {
auths = Collections.singletonList(new AuthorizationValue(headers[0], headers[1], "header"));
}

ChangedOpenApi result = OpenApiCompare.fromLocations(oldPath, newPath, auths);
OpenApiDiffOptions.Builder optionBuilder = OpenApiDiffOptions.builder();
String[] configFilePaths = line.getOptionValues("config-file");
if (configFilePaths != null) {
for (String configFilePath : configFilePaths) {
optionBuilder.configYaml(new File(configFilePath));
}
}

String[] configProps = line.getOptionValues("config-prop");
if (configProps != null) {
for (String propKeyAndVal : configProps) {
String[] split = propKeyAndVal.split(":");
if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) {
throw new IllegalArgumentException("--config-prop unexpected format: " + propKeyAndVal);
}
optionBuilder.configProperty(split[0], split[1]);
}
}
OpenApiDiffOptions compareOpts = optionBuilder.build();

ChangedOpenApi result = OpenApiCompare.fromLocations(oldPath, newPath, auths, compareOpts);
ConsoleRender consoleRender = new ConsoleRender();
if (!logLevel.equals("OFF")) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Expand Down
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.File;
import java.util.List;
import org.openapitools.openapidiff.core.compare.OpenApiDiff;
import org.openapitools.openapidiff.core.compare.OpenApiDiffOptions;
import org.openapitools.openapidiff.core.model.ChangedOpenApi;

public class OpenApiCompare {
Expand Down Expand Up @@ -40,7 +41,25 @@ public static ChangedOpenApi fromContents(String oldContent, String newContent)
*/
public static ChangedOpenApi fromContents(
String oldContent, String newContent, List<AuthorizationValue> auths) {
return fromSpecifications(readContent(oldContent, auths), readContent(newContent, auths));
return fromContents(oldContent, newContent, auths, OpenApiDiffOptions.builder().build());
}

/**
* compare two openapi doc
*
* @param oldContent old api-doc location:Json or Http
* @param newContent new api-doc location:Json or Http
* @param auths
* @param options
* @return Comparison result
*/
public static ChangedOpenApi fromContents(
String oldContent,
String newContent,
List<AuthorizationValue> auths,
OpenApiDiffOptions options) {
return fromSpecifications(
readContent(oldContent, auths), readContent(newContent, auths), options);
}

/**
Expand All @@ -64,7 +83,21 @@ public static ChangedOpenApi fromFiles(File oldFile, File newFile) {
*/
public static ChangedOpenApi fromFiles(
File oldFile, File newFile, List<AuthorizationValue> auths) {
return fromLocations(oldFile.getAbsolutePath(), newFile.getAbsolutePath(), auths);
return fromFiles(oldFile, newFile, auths, OpenApiDiffOptions.builder().build());
}

/**
* compare two openapi doc
*
* @param oldFile old api-doc file
* @param newFile new api-doc file
* @param auths
* @param options
* @return Comparison result
*/
public static ChangedOpenApi fromFiles(
File oldFile, File newFile, List<AuthorizationValue> auths, OpenApiDiffOptions options) {
return fromLocations(oldFile.getAbsolutePath(), newFile.getAbsolutePath(), auths, options);
}

/**
Expand All @@ -88,7 +121,25 @@ public static ChangedOpenApi fromLocations(String oldLocation, String newLocatio
*/
public static ChangedOpenApi fromLocations(
String oldLocation, String newLocation, List<AuthorizationValue> auths) {
return fromSpecifications(readLocation(oldLocation, auths), readLocation(newLocation, auths));
return fromLocations(oldLocation, newLocation, auths, OpenApiDiffOptions.builder().build());
}

/**
* compare two openapi doc
*
* @param oldLocation old api-doc location (local or http)
* @param newLocation new api-doc location (local or http)
* @param auths
* @param options
* @return Comparison result
*/
public static ChangedOpenApi fromLocations(
String oldLocation,
String newLocation,
List<AuthorizationValue> auths,
OpenApiDiffOptions options) {
return fromSpecifications(
readLocation(oldLocation, auths), readLocation(newLocation, auths), options);
}

/**
Expand All @@ -99,7 +150,20 @@ public static ChangedOpenApi fromLocations(
* @return Comparison result
*/
public static ChangedOpenApi fromSpecifications(OpenAPI oldSpec, OpenAPI newSpec) {
return OpenApiDiff.compare(notNull(oldSpec, "old"), notNull(newSpec, "new"));
return fromSpecifications(oldSpec, newSpec, OpenApiDiffOptions.builder().build());
}

/**
* compare two openapi doc
*
* @param oldSpec old api-doc specification
* @param newSpec new api-doc specification
* @param options
* @return Comparison result
*/
public static ChangedOpenApi fromSpecifications(
OpenAPI oldSpec, OpenAPI newSpec, OpenApiDiffOptions options) {
return OpenApiDiff.compare(notNull(oldSpec, "old"), notNull(newSpec, "new"), options);
}

private static OpenAPI notNull(OpenAPI spec, String type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Objects;
import java.util.Optional;
import org.openapitools.openapidiff.core.model.ChangedOAuthFlow;
import org.openapitools.openapidiff.core.model.DiffContext;

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

public Optional<ChangedOAuthFlow> diff(OAuthFlow left, OAuthFlow right) {
ChangedOAuthFlow changedOAuthFlow = new ChangedOAuthFlow(left, right);
public Optional<ChangedOAuthFlow> diff(OAuthFlow left, OAuthFlow right, DiffContext context) {
ChangedOAuthFlow changedOAuthFlow = new ChangedOAuthFlow(left, right, context);
if (left != null && right != null) {
changedOAuthFlow
.setAuthorizationUrl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Map;
import java.util.Optional;
import org.openapitools.openapidiff.core.model.ChangedOAuthFlows;
import org.openapitools.openapidiff.core.model.DiffContext;

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

public Optional<ChangedOAuthFlows> diff(OAuthFlows left, OAuthFlows right) {
public Optional<ChangedOAuthFlows> diff(OAuthFlows left, OAuthFlows right, DiffContext context) {
ChangedOAuthFlows changedOAuthFlows = new ChangedOAuthFlows(left, right);
if (left != null && right != null) {
openApiDiff
.getOAuthFlowDiff()
.diff(left.getImplicit(), right.getImplicit())
.diff(left.getImplicit(), right.getImplicit(), context)
.ifPresent(changedOAuthFlows::setImplicitOAuthFlow);
openApiDiff
.getOAuthFlowDiff()
.diff(left.getPassword(), right.getPassword())
.diff(left.getPassword(), right.getPassword(), context)
.ifPresent(changedOAuthFlows::setPasswordOAuthFlow);
openApiDiff
.getOAuthFlowDiff()
.diff(left.getClientCredentials(), right.getClientCredentials())
.diff(left.getClientCredentials(), right.getClientCredentials(), context)
.ifPresent(changedOAuthFlows::setClientCredentialOAuthFlow);
openApiDiff
.getOAuthFlowDiff()
.diff(left.getAuthorizationCode(), right.getAuthorizationCode())
.diff(left.getAuthorizationCode(), right.getAuthorizationCode(), context)
.ifPresent(changedOAuthFlows::setAuthorizationCodeOAuthFlow);
}
openApiDiff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class OpenApiDiff {
private MetadataDiff metadataDiff;
private final OpenAPI oldSpecOpenApi;
private final OpenAPI newSpecOpenApi;
private final OpenApiDiffOptions options;
private List<Endpoint> newEndpoints;
private List<Endpoint> missingEndpoints;
private List<ChangedOperation> changedOperations;
Expand All @@ -50,18 +51,24 @@ public class OpenApiDiff {
/*
* @param oldSpecOpenApi
* @param newSpecOpenApi
* @param diffOptions
*/
private OpenApiDiff(OpenAPI oldSpecOpenApi, OpenAPI newSpecOpenApi) {
private OpenApiDiff(OpenAPI oldSpecOpenApi, OpenAPI newSpecOpenApi, OpenApiDiffOptions options) {
this.oldSpecOpenApi = oldSpecOpenApi;
this.newSpecOpenApi = newSpecOpenApi;
this.options = options;
if (null == oldSpecOpenApi || null == newSpecOpenApi) {
throw new RuntimeException("one of the old or new object is null");
}
if (null == options) {
throw new IllegalArgumentException("options parameter is null but is required");
}
initializeFields();
}

public static ChangedOpenApi compare(OpenAPI oldSpec, OpenAPI newSpec) {
return new OpenApiDiff(oldSpec, newSpec).compare();
public static ChangedOpenApi compare(
OpenAPI oldSpec, OpenAPI newSpec, OpenApiDiffOptions diffOptions) {
return new OpenApiDiff(oldSpec, newSpec, diffOptions).compare();
}

private void initializeFields() {
Expand All @@ -87,6 +94,10 @@ private void initializeFields() {
this.deferredSchemaCache = new DeferredSchemaCache(this);
}

public OpenApiDiffOptions getOptions() {
return options;
}

private ChangedOpenApi compare() {
preProcess(oldSpecOpenApi);
preProcess(newSpecOpenApi);
Expand Down Expand Up @@ -163,7 +174,7 @@ private void preProcess(OpenAPI openApi) {
}

private ChangedOpenApi getChangedOpenApi() {
return new ChangedOpenApi()
return new ChangedOpenApi(options)
.setMissingEndpoints(missingEndpoints)
.setNewEndpoints(newEndpoints)
.setNewSpecOpenApi(newSpecOpenApi)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.openapitools.openapidiff.core.compare;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import org.apache.commons.configuration2.CompositeConfiguration;
import org.apache.commons.configuration2.YAMLConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;

public class OpenApiDiffOptions {
private final CompositeConfiguration config;

private OpenApiDiffOptions(CompositeConfiguration config) {
this.config = config;
}

public CompositeConfiguration getConfig() {
return config;
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
private OpenApiDiffOptions built = new OpenApiDiffOptions(new CompositeConfiguration());

public Builder configYaml(File file) {
YAMLConfiguration yamlConfig = new YAMLConfiguration();
try {
yamlConfig.read(new FileReader(file));
} catch (ConfigurationException | FileNotFoundException e) {
throw new IllegalArgumentException("Problem loading config. file=" + file, e);
}
// Ideally immutable, but since it isn't, we just modify the config directly
built.getConfig().addConfigurationFirst(yamlConfig);
return this;
}

public Builder configProperty(String propKey, String propVal) {
built.getConfig().setProperty(propKey, propVal);
return this;
}

public OpenApiDiffOptions build() {
return built;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public DeferredChanged<ChangedPaths> diff(
final Map<String, PathItem> left, final Map<String, PathItem> right) {
DeferredBuilder<Changed> builder = new DeferredBuilder<>();

ChangedPaths changedPaths = new ChangedPaths(left, right);
ChangedPaths changedPaths = new ChangedPaths(left, right, openApiDiff.getOptions());
changedPaths.getIncreased().putAll(right);

left.keySet()
Expand Down Expand Up @@ -82,7 +82,7 @@ public DeferredChanged<ChangedPaths> diff(
params.put(oldParams.get(i), newParams.get(i));
}
}
DiffContext context = new DiffContext();
DiffContext context = new DiffContext(openApiDiff.getOptions());
context.setUrl(url);
context.setParameters(params);
context.setLeftAndRightUrls(url, rightUrl);
Expand Down
Loading