diff --git a/src/main/java/com/qdesrame/openapi/diff/output/ConsoleRender.java b/src/main/java/com/qdesrame/openapi/diff/output/ConsoleRender.java index 6cbd9e6be..7fe4d8a08 100644 --- a/src/main/java/com/qdesrame/openapi/diff/output/ConsoleRender.java +++ b/src/main/java/com/qdesrame/openapi/diff/output/ConsoleRender.java @@ -3,6 +3,10 @@ import static com.qdesrame.openapi.diff.model.Changed.result; import com.qdesrame.openapi.diff.model.*; +import com.qdesrame.openapi.diff.utils.RefPointer; +import com.qdesrame.openapi.diff.utils.RefType; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.responses.ApiResponse; import java.util.List; @@ -13,11 +17,14 @@ public class ConsoleRender implements Render { private static final int LINE_LENGTH = 74; + protected static RefPointer refPointer = new RefPointer<>(RefType.SCHEMAS); + protected ChangedOpenApi diff; private StringBuilder output; @Override public String render(ChangedOpenApi diff) { + this.diff = diff; output = new StringBuilder(); if (diff.isUnchanged()) { output.append("No differences. Specifications are equivalents"); @@ -168,9 +175,73 @@ private String itemContent( .append("Schema: ") .append(changedMediaType.isCompatible() ? "Backward compatible" : "Broken compatibility") .append(System.lineSeparator()); + if (!changedMediaType.isCompatible()) { + sb.append(incompatibilities(changedMediaType.getSchema())); + } + return sb.toString(); + } + + private String incompatibilities(final ChangedSchema schema) { + return incompatibilities("", schema); + } + + private String incompatibilities(String propName, final ChangedSchema schema) { + StringBuilder sb = new StringBuilder(); + if (schema.getItems() != null) { + sb.append(items(propName, schema.getItems())); + } + if (schema.isCoreChanged() == DiffResult.INCOMPATIBLE && schema.isChangedType()) { + String type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema()); + sb.append(property(propName, "Changed property type", type)); + } + String prefix = propName.isEmpty() ? "" : propName + "."; + sb.append( + properties(prefix, "Missing property", schema.getMissingProperties(), schema.getContext())); + schema + .getChangedProperties() + .forEach((name, property) -> sb.append(incompatibilities(prefix + name, property))); return sb.toString(); } + private String items(String propName, ChangedSchema schema) { + StringBuilder sb = new StringBuilder(); + sb.append(incompatibilities(propName + "[n]", schema)); + return sb.toString(); + } + + private String properties( + String propPrefix, String title, Map properties, DiffContext context) { + StringBuilder sb = new StringBuilder(); + if (properties != null) { + properties.forEach( + (key, value) -> sb.append(property(propPrefix + key, title, resolve(value)))); + } + return sb.toString(); + } + + protected String property(String name, String title, Schema schema) { + return property(name, title, type(schema)); + } + + protected String property(String name, String title, String type) { + return String.format("%s%s: %s (%s)\n", StringUtils.repeat(' ', 10), title, name, type); + } + + protected Schema resolve(Schema schema) { + return refPointer.resolveRef( + diff.getNewSpecOpenApi().getComponents(), schema, schema.get$ref()); + } + + protected String type(Schema schema) { + String result = "object"; + if (schema instanceof ArraySchema) { + result = "array"; + } else if (schema.getType() != null) { + result = schema.getType(); + } + return result; + } + private String ul_param(ChangedParameters changedParameters) { List addParameters = changedParameters.getIncreased(); List delParameters = changedParameters.getMissing(); diff --git a/src/main/java/com/qdesrame/openapi/diff/output/HtmlRender.java b/src/main/java/com/qdesrame/openapi/diff/output/HtmlRender.java index b2c0d3516..756bc045a 100644 --- a/src/main/java/com/qdesrame/openapi/diff/output/HtmlRender.java +++ b/src/main/java/com/qdesrame/openapi/diff/output/HtmlRender.java @@ -4,7 +4,11 @@ import static j2html.TagCreator.*; import com.qdesrame.openapi.diff.model.*; +import com.qdesrame.openapi.diff.utils.RefPointer; +import com.qdesrame.openapi.diff.utils.RefType; +import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.responses.ApiResponse; import j2html.tags.ContainerTag; @@ -16,6 +20,8 @@ public class HtmlRender implements Render { private String title; private String linkCss; + protected static RefPointer refPointer = new RefPointer<>(RefType.SCHEMAS); + protected ChangedOpenApi diff; public HtmlRender() { this("Api Change Log", "http://deepoove.com/swagger-diff/stylesheets/demo.css"); @@ -27,6 +33,8 @@ public HtmlRender(String title, String linkCss) { } public String render(ChangedOpenApi diff) { + this.diff = diff; + List newEndpoints = diff.getNewEndpoints(); ContainerTag ol_newEndpoint = ol_newEndpoint(newEndpoints); @@ -214,16 +222,80 @@ private ContainerTag li_missingRequest(String name, MediaType request) { } private ContainerTag li_changedRequest(String name, ChangedMediaType request) { - return li().withText(String.format("Changed body: '%s'", name)) - .with(div_changedSchema(request.getSchema())); + ContainerTag li = + li().with(div_changedSchema(request.getSchema())) + .withText(String.format("Changed body: '%s'", name)); + if (request.isIncompatible()) { + incompatibilities(li, request.getSchema()); + } + return li; } private ContainerTag div_changedSchema(ChangedSchema schema) { ContainerTag div = div(); - div.with(h3("Schema")); + div.with(h3("Schema" + (schema.isIncompatible() ? " incompatible" : ""))); return div; } + private void incompatibilities(final ContainerTag output, final ChangedSchema schema) { + incompatibilities(output, "", schema); + } + + private void incompatibilities( + final ContainerTag output, String propName, final ChangedSchema schema) { + if (schema.getItems() != null) { + items(output, propName, schema.getItems()); + } + if (schema.isCoreChanged() == DiffResult.INCOMPATIBLE && schema.isChangedType()) { + String type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema()); + property(output, propName, "Changed property type", type); + } + String prefix = propName.isEmpty() ? "" : propName + "."; + properties( + output, prefix, "Missing property", schema.getMissingProperties(), schema.getContext()); + schema + .getChangedProperties() + .forEach((name, property) -> incompatibilities(output, prefix + name, property)); + } + + private void items(ContainerTag output, String propName, ChangedSchema schema) { + incompatibilities(output, propName + "[n]", schema); + } + + private void properties( + ContainerTag output, + String propPrefix, + String title, + Map properties, + DiffContext context) { + if (properties != null) { + properties.forEach((key, value) -> property(output, propPrefix + key, title, resolve(value))); + } + } + + protected void property(ContainerTag output, String name, String title, Schema schema) { + property(output, name, title, type(schema)); + } + + protected void property(ContainerTag output, String name, String title, String type) { + output.with(p(String.format("%s: %s (%s)", title, name, type)).withClass("missing")); + } + + protected Schema resolve(Schema schema) { + return refPointer.resolveRef( + diff.getNewSpecOpenApi().getComponents(), schema, schema.get$ref()); + } + + protected String type(Schema schema) { + String result = "object"; + if (schema instanceof ArraySchema) { + result = "array"; + } else if (schema.getType() != null) { + result = schema.getType(); + } + return result; + } + private ContainerTag ul_param(ChangedParameters changedParameters) { List addParameters = changedParameters.getIncreased(); List delParameters = changedParameters.getMissing();