diff --git a/cli/pom.xml b/cli/pom.xml index cdc684fe..08c89788 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -4,7 +4,7 @@ org.openapitools.openapidiff openapi-diff-parent - 2.1.5 + 2.1.6 openapi-diff-cli diff --git a/core/pom.xml b/core/pom.xml index 3856a5fe..3ae4ae7c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ org.openapitools.openapidiff openapi-diff-parent - 2.1.5 + 2.1.6 openapi-diff-core diff --git a/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java b/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java index dd3c8ab3..67d976a3 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/compare/schemadiffresult/SchemaDiffResult.java @@ -66,6 +66,7 @@ public , X> DeferredChanged diff( .setReadOnly(new ChangedReadOnly(left.getReadOnly(), right.getReadOnly(), context)) .setWriteOnly(new ChangedWriteOnly(left.getWriteOnly(), right.getWriteOnly(), context)) .setMaxLength(new ChangedMaxLength(left.getMaxLength(), right.getMaxLength(), context)) + .setMinLength(new ChangedMinLength(left.getMinLength(), right.getMinLength(), context)) .setNumericRange( new ChangedNumericRange( left.getMinimum(), diff --git a/core/src/main/java/org/openapitools/openapidiff/core/model/BackwardIncompatibleProp.java b/core/src/main/java/org/openapitools/openapidiff/core/model/BackwardIncompatibleProp.java index f2180b34..b3da97ca 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/model/BackwardIncompatibleProp.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/model/BackwardIncompatibleProp.java @@ -14,6 +14,7 @@ public enum BackwardIncompatibleProp { REQUEST_CONTENT_DECREASED("incompatible.request.content.decreased", true), REQUEST_ENUM_DECREASED("incompatible.request.enum.decreased", true), REQUEST_MAX_LENGTH_DECREASED("incompatible.request.max.length.decreased", true), + REQUEST_MIN_LENGTH_INCREASED("incompatible.request.min.length.increased", false), REQUEST_NUMERIC_RANGE_DECREASED("incompatible.request.numeric.range.decreased", true), REQUEST_ONEOF_DECREASED("incompatible.request.oneof.decreased", true), REQUEST_PARAM_ALLOWEMPTY_DECREASED("incompatible.request.param.allowempty.decreased", true), @@ -31,6 +32,7 @@ public enum BackwardIncompatibleProp { RESPONSE_HEADER_REQUIRED_INCREASED("incompatible.response.header.required.increased", true), RESPONSE_HEADERS_DECREASED("incompatible.response.headers.decreased", true), RESPONSE_MAX_LENGTH_INCREASED("incompatible.response.max.length.increased", true), + RESPONSE_MIN_LENGTH_INCREASED("incompatible.response.min.length.increased", false), RESPONSE_NUMERIC_RANGE_INCREASED("incompatible.response.numeric.range.increased", false), RESPONSE_ONEOF_INCREASED("incompatible.response.oneof.increased", true), RESPONSE_REQUIRED_DECREASED("incompatible.response.required.decreased", true), diff --git a/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java b/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java index 1896783a..30d126fb 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/model/ChangedSchema.java @@ -17,6 +17,7 @@ import org.openapitools.openapidiff.core.model.schema.ChangedMaxLength; import org.openapitools.openapidiff.core.model.schema.ChangedMaxProperties; import org.openapitools.openapidiff.core.model.schema.ChangedMinItems; +import org.openapitools.openapidiff.core.model.schema.ChangedMinLength; import org.openapitools.openapidiff.core.model.schema.ChangedMinProperties; import org.openapitools.openapidiff.core.model.schema.ChangedMultipleOf; import org.openapitools.openapidiff.core.model.schema.ChangedNullable; @@ -49,6 +50,7 @@ public class ChangedSchema implements ComposedChanged { protected ChangedWriteOnly writeOnly; protected boolean changedType; protected ChangedMaxLength maxLength; + protected ChangedMinLength minLength; protected ChangedNumericRange numericRange; protected ChangedMultipleOf multipleOf; protected ChangedMaxItems maxItems; @@ -139,6 +141,7 @@ public List getChangedElements() { enumeration, required, maxLength, + minLength, numericRange, multipleOf, maxItems, @@ -303,6 +306,10 @@ public ChangedMaxLength getMaxLength() { return this.maxLength; } + public ChangedMinLength getMinLength() { + return this.minLength; + } + public ChangedNumericRange getNumericRange() { return this.numericRange; } @@ -475,6 +482,12 @@ public ChangedSchema setMaxLength(final ChangedMaxLength maxLength) { return this; } + public ChangedSchema setMinLength(final ChangedMinLength minLength) { + clearChangedCache(); + this.minLength = minLength; + return this; + } + public ChangedSchema setNumericRange(final ChangedNumericRange numericRange) { clearChangedCache(); this.numericRange = numericRange; @@ -585,6 +598,7 @@ public boolean equals(Object o) { && Objects.equals(readOnly, that.readOnly) && Objects.equals(writeOnly, that.writeOnly) && Objects.equals(maxLength, that.maxLength) + && Objects.equals(minLength, that.minLength) && Objects.equals(numericRange, that.numericRange) && Objects.equals(multipleOf, that.multipleOf) && Objects.equals(maxItems, that.maxItems) @@ -623,6 +637,7 @@ public int hashCode() { writeOnly, changedType, maxLength, + minLength, numericRange, multipleOf, maxItems, @@ -681,6 +696,8 @@ public java.lang.String toString() { + this.isChangedType() + ", maxLength=" + this.getMaxLength() + + ", minLength=" + + this.getMinLength() + ", numericRange=" + this.getNumericRange() + ", multipleOf=" diff --git a/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedMinLength.java b/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedMinLength.java new file mode 100644 index 00000000..a964f4a1 --- /dev/null +++ b/core/src/main/java/org/openapitools/openapidiff/core/model/schema/ChangedMinLength.java @@ -0,0 +1,78 @@ +package org.openapitools.openapidiff.core.model.schema; + +import static org.openapitools.openapidiff.core.model.BackwardIncompatibleProp.REQUEST_MIN_LENGTH_INCREASED; +import static org.openapitools.openapidiff.core.model.BackwardIncompatibleProp.RESPONSE_MIN_LENGTH_INCREASED; + +import java.util.Objects; +import org.openapitools.openapidiff.core.model.Changed; +import org.openapitools.openapidiff.core.model.DiffContext; +import org.openapitools.openapidiff.core.model.DiffResult; + +public final class ChangedMinLength implements Changed { + private final Integer oldValue; + private final Integer newValue; + private final DiffContext context; + + @Override + public DiffResult isChanged() { + if (Objects.equals(oldValue, newValue)) { + return DiffResult.NO_CHANGES; + } + if (context.isRequest() && (newValue != null && (oldValue == null || newValue > oldValue))) { + if (REQUEST_MIN_LENGTH_INCREASED.enabled(context)) { + return DiffResult.INCOMPATIBLE; + } + } + if (context.isResponse() && (newValue != null && (oldValue == null || newValue > oldValue))) { + if (RESPONSE_MIN_LENGTH_INCREASED.enabled(context)) { + return DiffResult.INCOMPATIBLE; + } + } + return DiffResult.COMPATIBLE; + } + + public ChangedMinLength( + final Integer oldValue, final Integer newValue, final DiffContext context) { + this.oldValue = oldValue; + this.newValue = newValue; + this.context = context; + } + + public Integer getOldValue() { + return this.oldValue; + } + + public Integer getNewValue() { + return this.newValue; + } + + public DiffContext getContext() { + return this.context; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChangedMinLength that = (ChangedMinLength) o; + return Objects.equals(oldValue, that.oldValue) + && Objects.equals(newValue, that.newValue) + && Objects.equals(context, that.context); + } + + @Override + public int hashCode() { + return Objects.hash(oldValue, newValue, context); + } + + @Override + public String toString() { + return "ChangedMinLength(oldValue=" + + this.getOldValue() + + ", newValue=" + + this.getNewValue() + + ", context=" + + this.getContext() + + ")"; + } +} diff --git a/core/src/main/java/org/openapitools/openapidiff/core/output/MarkdownRender.java b/core/src/main/java/org/openapitools/openapidiff/core/output/MarkdownRender.java index e55179b1..b1182606 100644 --- a/core/src/main/java/org/openapitools/openapidiff/core/output/MarkdownRender.java +++ b/core/src/main/java/org/openapitools/openapidiff/core/output/MarkdownRender.java @@ -51,10 +51,16 @@ public class MarkdownRender implements Render { public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) { this.diff = diff; this.handledSchemas.clear(); + + apiTitle( + diff.getNewSpecOpenApi().getInfo().getTitle(), + diff.getNewSpecOpenApi().getInfo().getVersion(), + outputStreamWriter); listEndpoints("What's New", diff.getNewEndpoints(), outputStreamWriter); listEndpoints("What's Deleted", diff.getMissingEndpoints(), outputStreamWriter); listEndpoints("What's Deprecated", diff.getDeprecatedEndpoints(), outputStreamWriter); listEndpoints(diff.getChangedOperations(), outputStreamWriter); + changeSummary("Result", diff, outputStreamWriter); try { outputStreamWriter.close(); } catch (IOException e) { @@ -62,6 +68,29 @@ public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) { } } + protected String bigTitle(String title) { + return H3 + title + '\n' + HR + '\n'; + } + + protected void apiTitle(String title, String version, OutputStreamWriter outputStreamWriter) { + safelyAppend(outputStreamWriter, bigTitle(title + " (v " + version + ")")); + } + + protected void changeSummary( + String title, ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) { + safelyAppend(outputStreamWriter, sectionTitle(title)); + + if (diff.isUnchanged()) { + safelyAppend(outputStreamWriter, "No differences. Specifications are equivalent"); + } else if (diff.isCompatible()) { + safelyAppend(outputStreamWriter, "API changes are backward compatible"); + } else { + safelyAppend(outputStreamWriter, "API changes broke backward compatibility"); + } + + safelyAppend(outputStreamWriter, "\n\n"); + } + protected String sectionTitle(String title) { return H4 + title + '\n' + HR + '\n'; } diff --git a/core/src/test/java/org/openapitools/openapidiff/core/backcompat/MinLengthBCTest.java b/core/src/test/java/org/openapitools/openapidiff/core/backcompat/MinLengthBCTest.java new file mode 100644 index 00000000..fbc792b6 --- /dev/null +++ b/core/src/test/java/org/openapitools/openapidiff/core/backcompat/MinLengthBCTest.java @@ -0,0 +1,41 @@ +package org.openapitools.openapidiff.core.backcompat; + +import static org.openapitools.openapidiff.core.TestUtils.assertSpecChangedButCompatible; +import static org.openapitools.openapidiff.core.TestUtils.assertSpecIncompatible; +import static org.openapitools.openapidiff.core.TestUtils.assertSpecUnchanged; +import static org.openapitools.openapidiff.core.model.BackwardIncompatibleProp.REQUEST_MIN_LENGTH_INCREASED; +import static org.openapitools.openapidiff.core.model.BackwardIncompatibleProp.RESPONSE_MIN_LENGTH_INCREASED; + +import org.junit.jupiter.api.Test; +import org.openapitools.openapidiff.core.model.BackwardIncompatibleProp; + +public class MinLengthBCTest { + private final String BASE = "bc_minlength_base.yaml"; + + @Test + public void minLengthUnchanged() { + assertSpecUnchanged(BASE, BASE); + } + + @Test + public void requestMinLengthIncreased() { + BackwardIncompatibleProp prop = REQUEST_MIN_LENGTH_INCREASED; + assertSpecIncompatible(BASE, "bc_request_minlength_increased.yaml", prop); + } + + @Test + public void requestMinLengthDecreasedButCompatible() { + assertSpecChangedButCompatible(BASE, "bc_request_minlength_changed_but_compatible.yaml"); + } + + @Test + public void responseMinLengthIncreased() { + BackwardIncompatibleProp prop = RESPONSE_MIN_LENGTH_INCREASED; + assertSpecIncompatible(BASE, "bc_response_minlength_increased.yaml", prop); + } + + @Test + public void responseMinLengthDecreasedButCompatible() { + assertSpecChangedButCompatible(BASE, "bc_response_minlength_changed_but_compatible.yaml"); + } +} diff --git a/core/src/test/resources/bc_minlength_base.yaml b/core/src/test/resources/bc_minlength_base.yaml new file mode 100644 index 00000000..3e61e691 --- /dev/null +++ b/core/src/test/resources/bc_minlength_base.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + description: myDesc + title: myTitle + version: 1.0.0 +paths: + /widgets: + post: + operationId: widgetCreate + requestBody: + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + diff --git a/core/src/test/resources/bc_request_minlength_changed_but_compatible.yaml b/core/src/test/resources/bc_request_minlength_changed_but_compatible.yaml new file mode 100644 index 00000000..663ee7c0 --- /dev/null +++ b/core/src/test/resources/bc_request_minlength_changed_but_compatible.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + description: myDesc + title: myTitle + version: 1.0.0 +paths: + /widgets: + post: + operationId: widgetCreate + requestBody: + content: + application/json: + schema: + type: string + minLength: 3 + application/xml: + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + diff --git a/core/src/test/resources/bc_request_minlength_increased.yaml b/core/src/test/resources/bc_request_minlength_increased.yaml new file mode 100644 index 00000000..f7760ba6 --- /dev/null +++ b/core/src/test/resources/bc_request_minlength_increased.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + description: myDesc + title: myTitle + version: 1.0.0 +paths: + /widgets: + post: + operationId: widgetCreate + requestBody: + content: + application/json: + schema: + type: string + minLength: 10 + application/xml: + schema: + type: string + minLength: 10 + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + diff --git a/core/src/test/resources/bc_response_minlength_changed_but_compatible.yaml b/core/src/test/resources/bc_response_minlength_changed_but_compatible.yaml new file mode 100644 index 00000000..a965540a --- /dev/null +++ b/core/src/test/resources/bc_response_minlength_changed_but_compatible.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + description: myDesc + title: myTitle + version: 1.0.0 +paths: + /widgets: + post: + operationId: widgetCreate + requestBody: + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: string + minLength: 3 + application/xml: + schema: + type: string + minLength: 3 + diff --git a/core/src/test/resources/bc_response_minlength_increased.yaml b/core/src/test/resources/bc_response_minlength_increased.yaml new file mode 100644 index 00000000..06197c03 --- /dev/null +++ b/core/src/test/resources/bc_response_minlength_increased.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + description: myDesc + title: myTitle + version: 1.0.0 +paths: + /widgets: + post: + operationId: widgetCreate + requestBody: + content: + application/json: + schema: + type: string + minLength: 5 + application/xml: + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: string + minLength: 10 + application/xml: + schema: + type: string + minLength: 10 + diff --git a/maven-example/pom.xml b/maven-example/pom.xml index b6938325..615b0024 100644 --- a/maven-example/pom.xml +++ b/maven-example/pom.xml @@ -3,7 +3,7 @@ openapi-diff-parent org.openapitools.openapidiff - 2.1.5 + 2.1.6 4.0.0 @@ -23,7 +23,7 @@ org.openapitools.openapidiff openapi-diff-maven - 2.1.5 + 2.1.6 diff --git a/maven/pom.xml b/maven/pom.xml index 79a072c4..9c631c78 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -5,7 +5,7 @@ org.openapitools.openapidiff openapi-diff-parent - 2.1.5 + 2.1.6 openapi-diff-maven @@ -56,7 +56,7 @@ org.apache.maven.plugins maven-plugin-plugin - 3.13.1 + 3.15.2 openapi-diff diff --git a/pom.xml b/pom.xml index 6187f0f0..3bade228 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.openapitools.openapidiff openapi-diff-parent - 2.1.5 + 2.1.6 pom openapi-diff-parent @@ -82,7 +82,7 @@ 1.8 UTF-8 UTF-8 - 2025-11-03T08:59:15Z + 2025-11-26T14:07:20Z github openapitools @@ -99,12 +99,12 @@ org.openapitools.openapidiff openapi-diff-core - 2.1.5 + 2.1.6 org.junit junit-bom - 5.13.4 + 5.14.1 pom import @@ -141,7 +141,7 @@ commons-cli commons-cli - 1.10.0 + 1.11.0 org.apache.commons @@ -304,7 +304,7 @@ org.apache.maven.plugins maven-release-plugin - 3.1.1 + 3.2.0 org.apache.maven.plugins @@ -354,7 +354,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 5.2.0.4988 + 5.3.0.6276