com.github.elibracha.output.MarkdownRender Maven / Gradle / Ivy
package com.github.elibracha.output;
import static com.github.elibracha.utils.ChangedUtils.isUnchanged;
import static java.lang.String.format;
import com.github.elibracha.model.*;
import com.github.elibracha.utils.RefPointer;
import com.github.elibracha.utils.RefType;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
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 java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MarkdownRender implements Render {
public static final Logger LOGGER = LoggerFactory.getLogger(MarkdownRender.class);
protected static RefPointer refPointer = new RefPointer<>(RefType.SCHEMAS);
protected final String H3 = "### ";
protected final String H4 = "#### ";
protected final String H5 = "##### ";
protected final String H6 = "###### ";
protected final String BLOCKQUOTE = "> ";
protected final String CODE = "`";
protected final String PRE_CODE = " ";
protected final String PRE_LI = " ";
protected final String LI = "* ";
protected final String HR = "---\n";
protected ChangedOpenApi diff;
/**
* A paramater which indicates whether or not metadata (summary and metadata) changes should be
* logged in the changelog file.
*/
@Getter @Setter protected boolean showChangedMetadata;
public MarkdownRender() {}
public String render(ChangedOpenApi diff) {
this.diff = diff;
return listEndpoints("What's New", diff.getNewEndpoints())
+ listEndpoints("What's Deleted", diff.getMissingEndpoints())
+ listEndpoints("What's Deprecated", diff.getDeprecatedEndpoints())
+ listEndpoints(diff.getChangedOperations());
}
protected String sectionTitle(String title) {
return H4 + title + '\n' + HR + '\n';
}
protected String listEndpoints(String title, List endpoints) {
if (null == endpoints || endpoints.size() == 0) return "";
StringBuilder sb = new StringBuilder(sectionTitle(title));
endpoints.stream()
.map(e -> itemEndpoint(e.getMethod().toString(), e.getPathUrl(), e.getSummary()))
.forEach(sb::append);
return sb.toString();
}
protected String itemEndpoint(String method, String path, String summary) {
return H5 + CODE + method + CODE + " " + path + "\n\n" + metadata(summary) + "\n";
}
protected String itemEndpoint(String method, String path, ChangedMetadata summary) {
return H5 + CODE + method + CODE + " " + path + "\n\n" + metadata("summary", summary) + "\n";
}
protected String titleH5(String title) {
return H6 + title + '\n';
}
protected String listEndpoints(List changedOperations) {
if (null == changedOperations || changedOperations.size() == 0) return "";
StringBuilder sb = new StringBuilder(sectionTitle("What's Changed"));
changedOperations.stream()
.map(
operation -> {
StringBuilder details =
new StringBuilder()
.append(
itemEndpoint(
operation.getHttpMethod().toString(),
operation.getPathUrl(),
operation.getSummary()));
if (Changed.result(operation.getParameters()).isDifferent()) {
details
.append(titleH5("Parameters:"))
.append(parameters(operation.getParameters()));
}
if (operation.resultRequestBody().isDifferent()) {
details
.append(titleH5("Request:"))
.append(metadata("Description", operation.getRequestBody().getDescription()))
.append(bodyContent(operation.getRequestBody().getContent()));
}
if (operation.resultApiResponses().isDifferent()) {
details
.append(titleH5("Return Type:"))
.append(responses(operation.getApiResponses()));
}
return details.toString();
})
.forEach(sb::append);
return sb.toString();
}
protected String responses(ChangedApiResponse changedApiResponse) {
StringBuilder sb = new StringBuilder("\n");
sb.append(listResponse("New response", changedApiResponse.getIncreased()));
sb.append(listResponse("Deleted response", changedApiResponse.getMissing()));
changedApiResponse.getChanged().entrySet().stream()
.map(e -> this.itemResponse(e.getKey(), e.getValue()))
.forEach(sb::append);
return sb.toString();
}
protected String listResponse(String title, Map responses) {
StringBuilder sb = new StringBuilder();
responses.entrySet().stream()
.map(e -> this.itemResponse(title, e.getKey(), e.getValue()))
.forEach(sb::append);
return sb.toString();
}
protected String itemResponse(String title, String code, ApiResponse response) {
return this.itemResponse(title, code, response.getDescription());
}
protected String itemResponse(String code, ChangedResponse response) {
StringBuilder sb = new StringBuilder();
sb.append(
this.itemResponse(
"Changed response",
code,
null == response.getNewApiResponse()
? ""
: response.getNewApiResponse().getDescription()));
sb.append(headers(response.getHeaders()));
if (response.getContent() != null) {
sb.append(this.bodyContent(LI, response.getContent()));
}
return sb.toString();
}
protected String itemResponse(String title, String code, String description) {
StringBuilder sb = new StringBuilder();
String status = "";
if (!code.equals("default")) {
status = HttpStatus.getStatusText(Integer.parseInt(code));
}
sb.append(format("%s : **%s %s**\n", title, code, status));
sb.append(metadata(description));
return sb.toString();
}
protected String headers(ChangedHeaders headers) {
StringBuilder sb = new StringBuilder();
if (headers != null) {
sb.append(listHeader("New header", headers.getIncreased()))
.append(listHeader("Deleted header", headers.getMissing()));
headers.getChanged().entrySet().stream()
.map(e -> this.itemHeader(e.getKey(), e.getValue()))
.forEach(sb::append);
}
return sb.toString();
}
protected String listHeader(String title, Map headers) {
StringBuilder sb = new StringBuilder();
headers.entrySet().stream()
.map(e -> this.itemHeader(title, e.getKey(), e.getValue()))
.forEach(sb::append);
return sb.toString();
}
protected String itemHeader(String title, String name, Header header) {
return this.itemHeader(title, name, header.getDescription());
}
protected String itemHeader(String code, ChangedHeader header) {
return this.itemHeader(
"Changed header",
code,
null == header.getNewHeader() ? "" : header.getNewHeader().getDescription());
}
protected String itemHeader(String title, String mediaType, String description) {
return format("%s : `%s`\n\n", title, mediaType) + metadata(description) + '\n';
}
protected String bodyContent(String prefix, ChangedContent changedContent) {
if (changedContent == null) {
return "";
}
StringBuilder sb = new StringBuilder("\n");
sb.append(listContent(prefix, "New content type", changedContent.getIncreased()));
sb.append(listContent(prefix, "Deleted content type", changedContent.getMissing()));
final int deepness;
if (StringUtils.isNotBlank(prefix)) {
deepness = 1;
} else {
deepness = 0;
}
changedContent.getChanged().entrySet().stream()
.map(e -> this.itemContent(deepness, e.getKey(), e.getValue()))
.forEach(e -> sb.append(prefix).append(e));
return sb.toString();
}
protected String bodyContent(ChangedContent changedContent) {
return bodyContent("", changedContent);
}
protected String listContent(String prefix, String title, Map mediaTypes) {
StringBuilder sb = new StringBuilder();
mediaTypes.entrySet().stream()
.map(e -> this.itemContent(title, e.getKey(), e.getValue()))
.forEach(e -> sb.append(prefix).append(e));
return sb.toString();
}
protected String itemContent(String title, String mediaType) {
return format("%s : `%s`\n\n", title, mediaType);
}
protected String itemContent(String title, String mediaType, MediaType content) {
return itemContent(title, mediaType);
}
protected String itemContent(int deepness, String mediaType, ChangedMediaType content) {
return itemContent("Changed content type", mediaType) + schema(deepness, content.getSchema());
}
protected String schema(ChangedSchema schema) {
return schema(1, schema);
}
protected String oneOfSchema(int deepness, ChangedOneOfSchema schema, String discriminator) {
StringBuilder sb = new StringBuilder();
schema
.getMissing()
.keySet()
.forEach(
key -> sb.append(format("%sDeleted '%s' %s\n", indent(deepness), key, discriminator)));
schema
.getIncreased()
.forEach(
(key, sub) ->
sb.append(format("%sAdded '%s' %s:\n", indent(deepness), key, discriminator))
.append(schema(deepness, sub, schema.getContext())));
schema
.getChanged()
.forEach(
(key, sub) ->
sb.append(format("%sUpdated `%s` %s:\n", indent(deepness), key, discriminator))
.append(schema(deepness, sub)));
return sb.toString();
}
protected String required(int deepness, String title, List required) {
StringBuilder sb = new StringBuilder();
if (required.size() > 0) {
sb.append(format("%s%s:\n", indent(deepness), title));
required.forEach(s -> sb.append(format("%s- `%s`\n", indent(deepness), s)));
sb.append("\n");
}
return sb.toString();
}
protected String schema(int deepness, ChangedSchema schema) {
StringBuilder sb = new StringBuilder();
if (schema.isDiscriminatorPropertyChanged()) {
LOGGER.debug("Discriminator property changed");
}
if (schema.getOneOfSchema() != null) {
String discriminator =
schema.getNewSchema().getDiscriminator() != null
? schema.getNewSchema().getDiscriminator().getPropertyName()
: "";
sb.append(oneOfSchema(deepness, schema.getOneOfSchema(), discriminator));
}
if (schema.getRequired() != null) {
sb.append(required(deepness, "New required properties", schema.getRequired().getIncreased()));
sb.append(required(deepness, "New optional properties", schema.getRequired().getMissing()));
}
if (schema.getItems() != null) {
sb.append(items(deepness, schema.getItems()));
}
sb.append(listDiff(deepness, "enum", schema.getEnumeration()));
sb.append(
properties(
deepness,
"Added property",
schema.getIncreasedProperties(),
true,
schema.getContext()));
sb.append(
properties(
deepness,
"Deleted property",
schema.getMissingProperties(),
false,
schema.getContext()));
schema
.getChangedProperties()
.forEach((name, property) -> sb.append(property(deepness, name, property)));
return sb.toString();
}
protected String schema(int deepness, ComposedSchema schema, DiffContext context) {
StringBuilder sb = new StringBuilder();
if (schema.getAllOf() != null && schema.getAllOf() != null) {
LOGGER.debug("All of schema");
schema.getAllOf().stream()
.map(this::resolve)
.forEach(composedChild -> sb.append(schema(deepness, composedChild, context)));
}
if (schema.getOneOf() != null && schema.getOneOf() != null) {
LOGGER.debug("One of schema");
sb.append(format("%sOne of:\n\n", indent(deepness)));
schema.getOneOf().stream()
.map(this::resolve)
.forEach(composedChild -> sb.append(schema(deepness + 1, composedChild, context)));
}
return sb.toString();
}
protected String schema(int deepness, Schema schema, DiffContext context) {
StringBuilder sb = new StringBuilder();
sb.append(listItem(deepness, "Enum", schema.getEnum()));
sb.append(properties(deepness, "Property", schema.getProperties(), true, context));
if (schema instanceof ComposedSchema) {
sb.append(schema(deepness, (ComposedSchema) schema, context));
} else if (schema instanceof ArraySchema) {
sb.append(items(deepness, resolve(((ArraySchema) schema).getItems()), context));
}
return sb.toString();
}
protected String items(int deepness, ChangedSchema schema) {
StringBuilder sb = new StringBuilder();
String type = type(schema.getNewSchema());
if (schema.isChangedType()) {
type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema());
}
sb.append(items(deepness, "Changed items", type, schema.getNewSchema().getDescription()));
sb.append(schema(deepness, schema));
return sb.toString();
}
protected String items(int deepness, Schema schema, DiffContext context) {
return items(deepness, "Items", type(schema), schema.getDescription())
+ schema(deepness, schema, context);
}
protected String items(int deepness, String title, String type, String description) {
return format(
"%s%s (%s):" + "\n%s\n",
indent(deepness), title, type, metadata(indent(deepness + 1), description));
}
protected String properties(
final int deepness,
String title,
Map properties,
boolean showContent,
DiffContext context) {
StringBuilder sb = new StringBuilder();
if (properties != null) {
properties.forEach(
(key, value) -> {
sb.append(property(deepness, title, key, resolve(value)));
if (showContent) {
sb.append(schema(deepness + 1, resolve(value), context));
}
});
}
return sb.toString();
}
protected String property(int deepness, String name, ChangedSchema schema) {
StringBuilder sb = new StringBuilder();
String type = type(schema.getNewSchema());
if (schema.isChangedType()) {
type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema());
}
sb.append(
property(deepness, "Changed property", name, type, schema.getNewSchema().getDescription()));
sb.append(schema(++deepness, schema));
return sb.toString();
}
protected String property(int deepness, String title, String name, Schema schema) {
return property(deepness, title, name, type(schema), schema.getDescription());
}
protected String property(
int deepness, String title, String name, String type, String description) {
return format(
"%s* %s `%s` (%s)\n%s\n",
indent(deepness), title, name, type, metadata(indent(deepness + 1), description));
}
protected String listDiff(int deepness, String name, ChangedList> listDiff) {
if (listDiff == null) {
return "";
}
return listItem(deepness, "Added " + name, listDiff.getIncreased())
+ listItem(deepness, "Removed " + name, listDiff.getMissing());
}
protected String listItem(int deepness, String name, List list) {
StringBuilder sb = new StringBuilder();
if (list != null && list.size() > 0) {
sb.append(format("%s%s value%s:\n\n", indent(deepness), name, list.size() > 1 ? "s" : ""));
list.forEach(p -> sb.append(format("%s* `%s`\n", indent(deepness), p)));
}
return sb.toString();
}
protected String parameters(ChangedParameters changedParameters) {
List changed = changedParameters.getChanged();
StringBuilder sb = new StringBuilder("\n");
sb.append(listParameter("Added", changedParameters.getIncreased()))
.append(listParameter("Deleted", changedParameters.getMissing()));
changed.stream().map(this::itemParameter).forEach(sb::append);
return sb.toString();
}
protected String listParameter(String title, List parameters) {
StringBuilder sb = new StringBuilder();
parameters.stream().map(p -> itemParameter(title, p)).forEach(sb::append);
return sb.toString();
}
protected String itemParameter(String title, Parameter parameter) {
return this.itemParameter(
title, parameter.getName(), parameter.getIn(), parameter.getDescription());
}
protected String itemParameter(String title, String name, String in, String description) {
return format("%s: ", title)
+ code(name)
+ " in "
+ code(in)
+ '\n'
+ metadata(description)
+ '\n';
}
protected String itemParameter(ChangedParameter param) {
Parameter rightParam = param.getNewParameter();
if (param.isDeprecated()) {
return itemParameter(
"Deprecated", rightParam.getName(), rightParam.getIn(), rightParam.getDescription());
}
return itemParameter(
"Changed", rightParam.getName(), rightParam.getIn(), rightParam.getDescription());
}
protected String code(String string) {
return CODE + string + CODE;
}
protected String metadata(String name, ChangedMetadata changedMetadata) {
return metadata("", name, changedMetadata);
}
protected String metadata(String beginning, String name, ChangedMetadata changedMetadata) {
if (changedMetadata == null) {
return "";
}
if (!isUnchanged(changedMetadata) && showChangedMetadata) {
return format(
"Changed %s:\n%s\nto:\n%s\n\n",
name,
metadata(beginning, changedMetadata.getLeft()),
metadata(beginning, changedMetadata.getRight()));
} else {
return metadata(beginning, name, changedMetadata.getRight());
}
}
protected String metadata(String metadata) {
return metadata("", metadata);
}
protected String metadata(String beginning, String name, String metadata) {
if (StringUtils.isBlank(metadata)) {
return "";
}
return blockquote(beginning, metadata);
}
protected String metadata(String beginning, String metadata) {
if (StringUtils.isBlank(metadata)) {
return "";
}
return blockquote(beginning, metadata);
}
protected String blockquote(String beginning) {
return beginning + BLOCKQUOTE;
}
protected String blockquote(String beginning, String text) {
String blockquote = blockquote(beginning);
return blockquote + text.trim().replaceAll("\n", "\n" + blockquote) + '\n';
}
protected String type(Schema schema) {
String result = "object";
if (schema instanceof ArraySchema) {
result = "array";
} else if (schema.getType() != null) {
result = schema.getType();
}
return result;
}
protected String indent(int deepness) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < deepness; i++) {
sb.append(PRE_LI);
}
return sb.toString();
}
protected Schema resolve(Schema schema) {
return refPointer.resolveRef(
diff.getNewSpecOpenApi().getComponents(), schema, schema.get$ref());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy