Skip to content

Commit 6f79b10

Browse files
nbiastauntonjoschi
andauthored
Add support for AsciiDoc Renderer (#569)
Co-authored-by: Jochen Schalanda <[email protected]>
1 parent a3cb2c3 commit 6f79b10

File tree

6 files changed

+460
-1
lines changed

6 files changed

+460
-1
lines changed

README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Compare two OpenAPI specifications (3.x) and render the difference to HTML plain
2323
* Depth comparison of parameters, responses, endpoint, http method (GET,POST,PUT,DELETE...)
2424
* Supports swagger api Authorization
2525
* Render difference of property with Expression Language
26-
* HTML, Markdown & JSON render
26+
* HTML, Markdown, Asciidoc & JSON render
2727

2828
# Maven
2929

@@ -44,6 +44,7 @@ Available on [Docker Hub](https://hub.docker.com/r/openapitools/openapi-diff/) a
4444
```bash
4545
# docker run openapitools/openapi-diff:latest
4646
usage: openapi-diff <old> <new>
47+
--asciidoc <file> export diff as asciidoc in given file
4748
--debug Print debugging information
4849
--error Print error information
4950
--fail-on-changed Fail if API changed but is backward
@@ -101,6 +102,7 @@ openapi-diff can read OpenAPI specs from JSON files or HTTP URLs.
101102
```bash
102103
$ openapi-diff --help
103104
usage: openapi-diff <old> <new>
105+
--asciidoc <file> export diff as asciidoc in given file
104106
--debug Print debugging information
105107
--error Print error information
106108
-h,--help print this message
@@ -204,6 +206,18 @@ try {
204206
}
205207
```
206208
209+
#### Asciidoc
210+
211+
```java
212+
String render = new AsciidocRender().render(diff);
213+
try {
214+
FileWriter fw = new FileWriter("testDiff.adoc");
215+
fw.write(render);
216+
fw.close();
217+
} catch (IOException e) {
218+
e.printStackTrace();
219+
}
220+
```
207221
208222
#### JSON
209223

cli/src/main/java/org/openapitools/openapidiff/cli/Main.java

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.apache.commons.lang3.exception.ExceptionUtils;
1818
import org.openapitools.openapidiff.core.OpenApiCompare;
1919
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
20+
import org.openapitools.openapidiff.core.output.AsciidocRender;
2021
import org.openapitools.openapidiff.core.output.ConsoleRender;
2122
import org.openapitools.openapidiff.core.output.HtmlRender;
2223
import org.openapitools.openapidiff.core.output.JsonRender;
@@ -88,6 +89,13 @@ public static void main(String... args) {
8889
.argName("file")
8990
.desc("export diff as markdown in given file")
9091
.build());
92+
options.addOption(
93+
Option.builder()
94+
.longOpt("asciidoc")
95+
.hasArg()
96+
.argName("file")
97+
.desc("export diff as asciidoc in given file")
98+
.build());
9199
options.addOption(
92100
Option.builder()
93101
.longOpt("html")
@@ -191,6 +199,12 @@ public static void main(String... args) {
191199
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
192200
mdRender.render(result, outputStreamWriter);
193201
}
202+
if (line.hasOption("asciidoc")) {
203+
AsciidocRender adocRender = new AsciidocRender();
204+
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("asciidoc"));
205+
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
206+
adocRender.render(result, outputStreamWriter);
207+
}
194208
if (line.hasOption("text")) {
195209
FileOutputStream outputStream = new FileOutputStream(line.getOptionValue("text"));
196210
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
package org.openapitools.openapidiff.core.output;
2+
3+
import static org.openapitools.openapidiff.core.model.Changed.result;
4+
5+
import io.swagger.v3.oas.models.media.ArraySchema;
6+
import io.swagger.v3.oas.models.media.Schema;
7+
import io.swagger.v3.oas.models.parameters.Parameter;
8+
import io.swagger.v3.oas.models.responses.ApiResponse;
9+
import java.io.IOException;
10+
import java.io.OutputStreamWriter;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Map.Entry;
14+
import java.util.Optional;
15+
import org.apache.commons.lang3.StringUtils;
16+
import org.openapitools.openapidiff.core.exception.RendererException;
17+
import org.openapitools.openapidiff.core.model.*;
18+
import org.openapitools.openapidiff.core.utils.RefPointer;
19+
import org.openapitools.openapidiff.core.utils.RefType;
20+
21+
public class AsciidocRender implements Render {
22+
private static final int LINE_LENGTH = 74;
23+
protected static RefPointer<Schema<?>> refPointer = new RefPointer<>(RefType.SCHEMAS);
24+
protected ChangedOpenApi diff;
25+
26+
@Override
27+
public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
28+
this.diff = diff;
29+
if (diff.isUnchanged()) {
30+
safelyAppend(
31+
outputStreamWriter,
32+
bigTitle(
33+
diff.getNewSpecOpenApi().getInfo().getTitle(),
34+
diff.getNewSpecOpenApi().getInfo().getVersion()));
35+
safelyAppend(outputStreamWriter, System.lineSeparator());
36+
safelyAppend(outputStreamWriter, System.lineSeparator());
37+
safelyAppend(outputStreamWriter, "NOTE: No differences. Specifications are equivalents");
38+
} else {
39+
safelyAppend(
40+
outputStreamWriter,
41+
bigTitle(
42+
diff.getNewSpecOpenApi().getInfo().getTitle(),
43+
diff.getNewSpecOpenApi().getInfo().getVersion()));
44+
safelyAppend(outputStreamWriter, System.lineSeparator());
45+
safelyAppend(outputStreamWriter, ":reproducible:\n:sectlinks:\n:toc:\n");
46+
safelyAppend(outputStreamWriter, System.lineSeparator());
47+
48+
List<Endpoint> newEndpoints = diff.getNewEndpoints();
49+
listEndpoints(newEndpoints, "What's New", outputStreamWriter);
50+
51+
List<Endpoint> missingEndpoints = diff.getMissingEndpoints();
52+
listEndpoints(missingEndpoints, "What's Deleted", outputStreamWriter);
53+
54+
List<Endpoint> deprecatedEndpoints = diff.getDeprecatedEndpoints();
55+
listEndpoints(deprecatedEndpoints, "What's Deprecated", outputStreamWriter);
56+
57+
List<ChangedOperation> changedOperations = diff.getChangedOperations();
58+
ol_changed(changedOperations, outputStreamWriter);
59+
60+
safelyAppend(outputStreamWriter, System.lineSeparator());
61+
safelyAppend(
62+
outputStreamWriter,
63+
diff.isCompatible()
64+
? "NOTE: API changes are backward compatible"
65+
: "WARNING: API changes broke backward compatibility");
66+
safelyAppend(outputStreamWriter, System.lineSeparator());
67+
}
68+
try {
69+
outputStreamWriter.close();
70+
} catch (IOException e) {
71+
throw new RendererException(e);
72+
}
73+
}
74+
75+
private void ol_changed(
76+
List<ChangedOperation> operations, OutputStreamWriter outputStreamWriter) {
77+
if (null == operations || operations.isEmpty()) {
78+
return;
79+
}
80+
safelyAppend(outputStreamWriter, title("What's Changed", 2));
81+
safelyAppend(outputStreamWriter, System.lineSeparator());
82+
for (ChangedOperation operation : operations) {
83+
String pathUrl = operation.getPathUrl();
84+
String method = operation.getHttpMethod().toString();
85+
String desc =
86+
Optional.ofNullable(operation.getSummary()).map(ChangedMetadata::getRight).orElse("");
87+
88+
safelyAppend(outputStreamWriter, itemEndpoint(method, pathUrl, desc));
89+
if (result(operation.getParameters()).isDifferent()) {
90+
safelyAppend(outputStreamWriter, "* Parameter:\n");
91+
safelyAppend(outputStreamWriter, ul_param(operation.getParameters()));
92+
safelyAppend(outputStreamWriter, System.lineSeparator());
93+
}
94+
if (operation.resultRequestBody().isDifferent()) {
95+
safelyAppend(outputStreamWriter, "* Request:\n");
96+
safelyAppend(
97+
outputStreamWriter, ul_content(operation.getRequestBody().getContent(), true, 2));
98+
safelyAppend(outputStreamWriter, System.lineSeparator());
99+
}
100+
if (operation.resultApiResponses().isDifferent()) {
101+
safelyAppend(outputStreamWriter, "* Return Type:\n");
102+
safelyAppend(outputStreamWriter, ul_response(operation.getApiResponses()));
103+
safelyAppend(outputStreamWriter, System.lineSeparator());
104+
}
105+
}
106+
}
107+
108+
private String ul_response(ChangedApiResponse changedApiResponse) {
109+
Map<String, ApiResponse> addResponses = changedApiResponse.getIncreased();
110+
Map<String, ApiResponse> delResponses = changedApiResponse.getMissing();
111+
Map<String, ChangedResponse> changedResponses = changedApiResponse.getChanged();
112+
StringBuilder sb = new StringBuilder();
113+
for (String propName : addResponses.keySet()) {
114+
sb.append(itemResponse("** Add ", propName));
115+
}
116+
for (String propName : delResponses.keySet()) {
117+
sb.append(itemResponse("** Deleted ", propName));
118+
}
119+
for (Entry<String, ChangedResponse> entry : changedResponses.entrySet()) {
120+
sb.append(itemChangedResponse("** Changed ", entry.getKey(), entry.getValue()));
121+
}
122+
return sb.toString();
123+
}
124+
125+
private String itemResponse(String title, String code) {
126+
StringBuilder sb = new StringBuilder();
127+
String status = "";
128+
if (!code.equals("default") && !code.matches("[1-5]XX")) {
129+
status = HttpStatus.getReasonPhrase(Integer.parseInt(code));
130+
}
131+
sb.append(title).append(code).append(' ').append(status).append("\n");
132+
return sb.toString();
133+
}
134+
135+
private String itemChangedResponse(String title, String contentType, ChangedResponse response) {
136+
return itemResponse(title, contentType)
137+
+ "** Media types:\n"
138+
+ ul_content(response.getContent(), false, 3);
139+
}
140+
141+
private String ul_content(ChangedContent changedContent, boolean isRequest, int indent) {
142+
StringBuilder sb = new StringBuilder();
143+
if (changedContent == null) {
144+
return sb.toString();
145+
}
146+
for (String propName : changedContent.getIncreased().keySet()) {
147+
sb.append(itemContent("Added ", propName, indent));
148+
}
149+
for (String propName : changedContent.getMissing().keySet()) {
150+
sb.append(itemContent("Deleted ", propName, indent));
151+
}
152+
for (String propName : changedContent.getChanged().keySet()) {
153+
sb.append(
154+
itemContent(
155+
"Changed ", propName, indent, changedContent.getChanged().get(propName), isRequest));
156+
}
157+
return sb.toString();
158+
}
159+
160+
private String itemContent(String title, String contentType, int indent) {
161+
return StringUtils.repeat('*', indent) + " " + title + contentType + "\n";
162+
}
163+
164+
private String itemContent(
165+
String title,
166+
String contentType,
167+
int indent,
168+
ChangedMediaType changedMediaType,
169+
boolean isRequest) {
170+
StringBuilder sb = new StringBuilder();
171+
sb.append(itemContent(title, contentType, indent))
172+
.append(itemContent("Schema:", "", indent))
173+
.append(changedMediaType.isCompatible() ? "Backward compatible" : "Broken compatibility")
174+
.append("\n");
175+
if (!changedMediaType.isCompatible()) {
176+
sb.append(incompatibilities(changedMediaType.getSchema()));
177+
}
178+
return sb.toString();
179+
}
180+
181+
private String incompatibilities(final ChangedSchema schema) {
182+
return incompatibilities("", schema);
183+
}
184+
185+
private String incompatibilities(String propName, final ChangedSchema schema) {
186+
StringBuilder sb = new StringBuilder();
187+
if (schema.getItems() != null) {
188+
sb.append(items(propName, schema.getItems()));
189+
}
190+
if (schema.isCoreChanged() == DiffResult.INCOMPATIBLE && schema.isChangedType()) {
191+
String type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema());
192+
sb.append(property(propName, "Changed property type", type));
193+
}
194+
String prefix = propName.isEmpty() ? "" : propName + ".";
195+
sb.append(
196+
properties(prefix, "Missing property", schema.getMissingProperties(), schema.getContext()));
197+
schema
198+
.getChangedProperties()
199+
.forEach((name, property) -> sb.append(incompatibilities(prefix + name, property)));
200+
return sb.toString();
201+
}
202+
203+
private String items(String propName, ChangedSchema schema) {
204+
return incompatibilities(propName + "[n]", schema);
205+
}
206+
207+
private String properties(
208+
String propPrefix, String title, Map<String, Schema<?>> properties, DiffContext context) {
209+
StringBuilder sb = new StringBuilder();
210+
if (properties != null) {
211+
properties.forEach((key, value) -> sb.append(resolveProperty(propPrefix, value, key, title)));
212+
}
213+
return sb.toString();
214+
}
215+
216+
private String resolveProperty(String propPrefix, Schema<?> value, String key, String title) {
217+
try {
218+
return property(propPrefix + key, title, resolve(value));
219+
} catch (Exception e) {
220+
return property(propPrefix + key, title, type(value));
221+
}
222+
}
223+
224+
protected String property(String name, String title, Schema<?> schema) {
225+
return property(name, title, type(schema));
226+
}
227+
228+
protected String property(String name, String title, String type) {
229+
return String.format("*** %s: %s (%s)%n\n", title, name, type);
230+
}
231+
232+
protected Schema<?> resolve(Schema<?> schema) {
233+
return refPointer.resolveRef(
234+
diff.getNewSpecOpenApi().getComponents(), schema, schema.get$ref());
235+
}
236+
237+
protected String type(Schema<?> schema) {
238+
String result = "object";
239+
if (schema == null) {
240+
result = "no schema";
241+
} else if (schema instanceof ArraySchema) {
242+
result = "array";
243+
} else if (schema.getType() != null) {
244+
result = schema.getType();
245+
}
246+
return result;
247+
}
248+
249+
private String ul_param(ChangedParameters changedParameters) {
250+
List<Parameter> addParameters = changedParameters.getIncreased();
251+
List<Parameter> delParameters = changedParameters.getMissing();
252+
List<ChangedParameter> changed = changedParameters.getChanged();
253+
StringBuilder sb = new StringBuilder();
254+
for (Parameter param : addParameters) {
255+
sb.append(itemParam("** Add ", param));
256+
}
257+
for (ChangedParameter param : changed) {
258+
sb.append(li_changedParam(param));
259+
}
260+
for (Parameter param : delParameters) {
261+
sb.append(itemParam("** Delete ", param));
262+
}
263+
return sb.toString();
264+
}
265+
266+
private String itemParam(String title, Parameter param) {
267+
return title + param.getName() + " in " + param.getIn() + System.lineSeparator();
268+
}
269+
270+
private String li_changedParam(ChangedParameter changeParam) {
271+
if (changeParam.isDeprecated()) {
272+
return itemParam("** Deprecated ", changeParam.getNewParameter());
273+
} else {
274+
return itemParam("** Changed ", changeParam.getNewParameter());
275+
}
276+
}
277+
278+
private String listEndpoints(
279+
List<Endpoint> endpoints, String title, OutputStreamWriter outputStreamWriter) {
280+
if (null == endpoints || endpoints.isEmpty()) {
281+
return "";
282+
}
283+
StringBuilder sb = new StringBuilder();
284+
sb.append(title(title));
285+
for (Endpoint endpoint : endpoints) {
286+
sb.append(
287+
itemEndpoint(
288+
endpoint.getMethod().toString(), endpoint.getPathUrl(), endpoint.getSummary()));
289+
}
290+
return sb.append(System.lineSeparator()).toString();
291+
}
292+
293+
private String itemEndpoint(String method, String path, String desc) {
294+
return String.format("=== %s%s%n", StringUtils.rightPad(method, 6), path);
295+
}
296+
297+
public String bigTitle(String title, String version) {
298+
char ch = '=';
299+
300+
return String.format("= %s (v %s)", title.toUpperCase(), version);
301+
}
302+
303+
public String title(String title) {
304+
return this.title(title, '-');
305+
}
306+
307+
public String title(String title, int level) {
308+
String little = StringUtils.repeat("=", level);
309+
return String.format("%s %s", little, title);
310+
}
311+
}

0 commit comments

Comments
 (0)