Skip to content

Commit ebd88ef

Browse files
committed
- code cleanup
- dynamic doc generation
1 parent 2e0e982 commit ebd88ef

File tree

15 files changed

+273
-83
lines changed

15 files changed

+273
-83
lines changed

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OpenAPIExt.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,6 @@ private <S, V> void setProperty(S src, Function<S, V> getter, S target, BiConsum
235235
}
236236
}
237237

238-
public OperationExt findOperationById(String operationId) {
239-
return getOperations().stream()
240-
.filter(it -> it.getOperationId().equals(operationId))
241-
.findFirst()
242-
.orElseThrow(() -> new IllegalArgumentException("Operation not found: " + operationId));
243-
}
244-
245238
public OperationExt findOperation(String method, String pattern) {
246239
Predicate<OperationExt> filter = op -> op.getPath().equals(pattern);
247240
filter = filter.and(op -> op.getMethod().equals(method));
@@ -251,4 +244,8 @@ public OperationExt findOperation(String method, String pattern) {
251244
.orElseThrow(
252245
() -> new IllegalArgumentException("Operation not found: " + method + " " + pattern));
253246
}
247+
248+
public List<OperationExt> findOperationByTag(String tag) {
249+
return getOperations().stream().filter(it -> it.isOnTag(tag)).toList();
250+
}
254251
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OperationExt.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ public void addTag(Tag tag) {
181181
addTagsItem(tag.getName());
182182
}
183183

184+
public boolean isOnTag(String tag) {
185+
return globalTags.stream().map(Tag::getName).anyMatch(tag::equals);
186+
}
187+
184188
public List<Tag> getGlobalTags() {
185189
return globalTags;
186190
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/AsciiDocContext.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class AsciiDocContext {
5858

5959
private final AutoDataFakerMapper faker = new AutoDataFakerMapper();
6060

61-
private final Map<Schema<?>, Map<String, Object>> examples = new HashMap<>();
61+
private final Map<Object, Map<String, Object>> examples = new HashMap<>();
6262

6363
private final Instant now = Instant.now();
6464

@@ -140,6 +140,19 @@ public Map<String, Object> getGlobalVariables() {
140140
openapiRoot.put("routes", operations);
141141
openapiRoot.put("operations", operations);
142142

143+
// Tags
144+
var tags =
145+
context.openapi.getTags().stream()
146+
.map(
147+
tag ->
148+
new TagExt(
149+
tag,
150+
context.openapi.findOperationByTag(tag.getName()).stream()
151+
.map(op -> new HttpRequest(context, op, Map.of()))
152+
.toList()))
153+
.toList();
154+
openapiRoot.put("tags", tags);
155+
143156
// make in to work without literal
144157
openapiRoot.put("query", "query");
145158
openapiRoot.put("path", "path");
@@ -259,7 +272,9 @@ public Map<String, Object> error(EvaluationContext context, Map<String, Object>
259272
"reason" ->
260273
statusCode.reason();
261274
case "status.code", "statusCode.code", "statusCode", "code" -> statusCode.value();
262-
default -> Optional.ofNullable(context.getVariable(variable)).orElse(template);
275+
default ->
276+
Optional.ofNullable(args.getOrDefault(variable, context.getVariable(variable)))
277+
.orElse(template);
263278
};
264279
mutableMap.put((String) entry.getKey(), value);
265280
}
@@ -332,7 +347,7 @@ public Map<String, Object> schemaExample(Schema<?> schema) {
332347
s ->
333348
traverse(
334349
new HashSet<>(),
335-
s,
350+
schema,
336351
(parent, property) -> {
337352
var enumItems = property.getEnum();
338353
if (enumItems == null || enumItems.isEmpty()) {

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/Display.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.HashMap;
99
import java.util.List;
1010
import java.util.Map;
11+
import java.util.TreeMap;
1112

1213
import io.jooby.internal.openapi.OperationExt;
1314
import io.jooby.internal.openapi.asciidoc.display.*;
@@ -74,12 +75,7 @@ public Object apply(
7475
int lineNumber)
7576
throws PebbleException {
7677
var asciidoc = AsciiDocContext.from(context);
77-
return new SafeString(toAsciidoc(asciidoc, input).table(args));
78-
}
79-
80-
@Override
81-
public List<String> getArgumentNames() {
82-
return List.of("columns");
78+
return new SafeString(toAsciidoc(asciidoc, input).table(new TreeMap<>(args)));
8379
}
8480
},
8581
list {
@@ -92,7 +88,7 @@ public Object apply(
9288
int lineNumber)
9389
throws PebbleException {
9490
var asciidoc = AsciiDocContext.from(context);
95-
return new SafeString(toAsciidoc(asciidoc, input).list(args));
91+
return new SafeString(toAsciidoc(asciidoc, input).list(new TreeMap<>(args)));
9692
}
9793
},
9894
curl {
@@ -183,6 +179,7 @@ protected ToAsciiDoc toAsciidoc(AsciiDocContext context, Object input) {
183179
protected Object toJson(AsciiDocContext context, Object input) {
184180
return switch (input) {
185181
case Schema<?> schema -> context.schemaProperties(schema);
182+
case HttpResponse rsp -> toJson(context, rsp.getSucessOrError());
186183
case StatusCodeList codeList ->
187184
codeList.codes().size() == 1 ? codeList.codes().getFirst() : codeList.codes();
188185
default -> input;

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/HttpRequest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ public String getPath() {
5757
return operation.getPath();
5858
}
5959

60+
public String getDescription() {
61+
return operation.getDescription();
62+
}
63+
64+
public String getSummary() {
65+
return operation.getSummary();
66+
}
67+
6068
public List<String> getProduces() {
6169
return operation.getProduces();
6270
}
@@ -262,8 +270,4 @@ private static Predicate<Parameter> inFilter(String in) {
262270
public String toString() {
263271
return getMethod() + " " + getPath();
264272
}
265-
266-
public String getSummary() {
267-
return operation.getSummary();
268-
}
269273
}

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/HttpRequestList.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.Map;
1111
import java.util.Optional;
12+
import java.util.stream.Collectors;
1213

1314
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
1415
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -46,7 +47,15 @@ public String list(Map<String, Object> options) {
4647
@Override
4748
public String table(Map<String, Object> options) {
4849
var sb = new StringBuilder();
49-
sb.append("[cols=\"1,1,3a\", options=\"header\"]").append('\n');
50+
if (options.isEmpty()) {
51+
options.put("options", "header");
52+
}
53+
options.putIfAbsent("cols", "1,1,3a");
54+
sb.append(
55+
options.entrySet().stream()
56+
.map(it -> it.getKey() + "=\"" + it.getValue() + "\"")
57+
.collect(Collectors.joining(", ", "[", "]")))
58+
.append('\n');
5059
sb.append("|===").append('\n');
5160
sb.append("|").append("Method|Path|Summary").append("\n\n");
5261
operations.forEach(

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/HttpResponse.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,23 @@
55
*/
66
package io.jooby.internal.openapi.asciidoc;
77

8+
import java.util.LinkedHashMap;
89
import java.util.List;
910
import java.util.Map;
1011
import java.util.Optional;
1112

12-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
13+
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
1314
import edu.umd.cs.findbugs.annotations.NonNull;
1415
import io.jooby.StatusCode;
1516
import io.jooby.internal.openapi.OperationExt;
1617
import io.jooby.internal.openapi.ParameterExt;
1718
import io.jooby.internal.openapi.ResponseExt;
19+
import io.pebbletemplates.pebble.template.EvaluationContext;
1820
import io.swagger.v3.oas.models.media.Schema;
1921

20-
@JsonIgnoreProperties({"context", "operation", "options"})
22+
@JsonIncludeProperties({"method", "path"})
2123
public record HttpResponse(
22-
AsciiDocContext context,
24+
EvaluationContext evaluationContext,
2325
OperationExt operation,
2426
Integer statusCode,
2527
Map<String, Object> options)
@@ -38,12 +40,47 @@ public ParameterList getCookies() {
3840
return new ParameterList(List.of(), ParameterList.NAME_DESC);
3941
}
4042

43+
@Override
44+
public AsciiDocContext context() {
45+
return AsciiDocContext.from(evaluationContext);
46+
}
47+
48+
public String getMethod() {
49+
return operation.getMethod();
50+
}
51+
52+
public String getPath() {
53+
return operation.getPath();
54+
}
55+
4156
@Override
4257
public Schema<?> getBody() {
43-
return selectBody(getBody(getResponse()), options.getOrDefault("body", "full").toString());
58+
return selectBody(getBody(response()), options.getOrDefault("body", "full").toString());
59+
}
60+
61+
public boolean isSuccess() {
62+
return statusCode != null && statusCode >= 200 && statusCode < 300;
63+
}
64+
65+
public Object getSucessOrError() {
66+
var response = response();
67+
if (response == operation.getDefaultResponse()) {
68+
return getBody();
69+
}
70+
// massage error apply global error format
71+
var rsp = operation.getResponses().get(Integer.toString(statusCode));
72+
73+
if (rsp == null) {
74+
// default output
75+
return context().error(evaluationContext, Map.of("code", statusCode));
76+
}
77+
var errorContext = new LinkedHashMap<String, Object>();
78+
errorContext.put("code", statusCode);
79+
errorContext.put("message", rsp.getDescription());
80+
return context().error(evaluationContext, errorContext);
4481
}
4582

46-
private ResponseExt getResponse() {
83+
private ResponseExt response() {
4784
if (statusCode == null) {
4885
return operation.getDefaultResponse();
4986
} else {
@@ -60,7 +97,7 @@ private ResponseExt getResponse() {
6097

6198
public StatusCode getStatusCode() {
6299
if (statusCode == null) {
63-
return Optional.ofNullable(getResponse())
100+
return Optional.ofNullable(response())
64101
.map(it -> StatusCode.valueOf(Integer.parseInt(it.getCode())))
65102
.orElse(StatusCode.OK);
66103
}
@@ -71,7 +108,7 @@ public StatusCode getStatusCode() {
71108
private Schema<?> getBody(ResponseExt response) {
72109
return Optional.ofNullable(response)
73110
.map(it -> toSchema(it.getContent(), List.of()))
74-
.map(context::resolveSchema)
111+
.map(context()::resolveSchema)
75112
.orElse(AsciiDocContext.EMPTY_SCHEMA);
76113
}
77114

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/Lookup.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ public Object execute(
129129
return asciidoc.getOpenApi().getTags().stream()
130130
.filter(tag -> tag.getName().equalsIgnoreCase(name))
131131
.findFirst()
132+
.map(
133+
it ->
134+
new TagExt(
135+
it,
136+
asciidoc.getOpenApi().findOperationByTag(it.getName()).stream()
137+
.map(op -> new HttpRequest(asciidoc, op, Map.of()))
138+
.toList()))
132139
.orElseThrow(() -> new NoSuchElementException("Tag not found: " + name));
133140
}
134141

modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/asciidoc/Mutator.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import io.pebbletemplates.pebble.extension.Filter;
1515
import io.pebbletemplates.pebble.template.EvaluationContext;
1616
import io.pebbletemplates.pebble.template.PebbleTemplate;
17-
import io.swagger.v3.oas.models.Operation;
1817
import io.swagger.v3.oas.models.media.Schema;
1918

2019
public enum Mutator implements Filter {
@@ -30,6 +29,8 @@ public Object apply(
3029
if (input instanceof Schema<?> schema) {
3130
var asciidoc = AsciiDocContext.from(context);
3231
return asciidoc.schemaExample(schema);
32+
} else if (input instanceof Map mapLike) {
33+
3334
}
3435
return input;
3536
}
@@ -72,14 +73,19 @@ public Object apply(
7273
int lineNumber)
7374
throws PebbleException {
7475
return new HttpResponse(
75-
AsciiDocContext.from(context),
76+
context,
7677
toOperation(input),
7778
Optional.ofNullable(args.get("code"))
7879
.map(Number.class::cast)
7980
.map(Number::intValue)
8081
.orElse(null),
8182
args);
8283
}
84+
85+
@Override
86+
public List<String> getArgumentNames() {
87+
return List.of("code");
88+
}
8389
},
8490
parameters {
8591
@Override
@@ -118,6 +124,11 @@ public Object apply(
118124
int lineNumber)
119125
throws PebbleException {
120126
var bodyType = args.getOrDefault("type", "full");
127+
// Handle response a bit different
128+
if (input instanceof HttpResponse rsp) {
129+
// success or error
130+
return rsp.getSucessOrError();
131+
}
121132
return toHttpMessage(context, input, Map.of("body", bodyType)).getBody();
122133
}
123134
},
@@ -135,20 +146,24 @@ public Object apply(
135146
};
136147

137148
protected OperationExt toOperation(Object input) {
138-
if (!(input instanceof OperationExt)) {
139-
throw new IllegalArgumentException(
140-
"Not an operation: " + input.getClass() + ", expecting: " + Operation.class);
141-
}
142-
return (OperationExt) input;
149+
return switch (input) {
150+
case OperationExt op -> op;
151+
case HttpRequest req -> req.operation();
152+
case HttpResponse rsp -> rsp.operation();
153+
case null -> throw new NullPointerException(name() + ": requires a request/response input");
154+
default ->
155+
throw new ClassCastException(
156+
name() + ": requires a request/response input: " + input.getClass());
157+
};
143158
}
144159

145160
protected HttpMessage toHttpMessage(
146161
EvaluationContext context, Object input, Map<String, Object> options) {
147162
return switch (input) {
148-
case null -> throw new NullPointerException(name() + ": requires a request/response input");
149163
// default to http request
150164
case OperationExt op -> new HttpRequest(AsciiDocContext.from(context), op, options);
151165
case HttpMessage msg -> msg;
166+
case null -> throw new NullPointerException(name() + ": requires a request/response input");
152167
default ->
153168
throw new ClassCastException(
154169
name() + ": requires a request/response input: " + input.getClass());
@@ -158,10 +173,10 @@ protected HttpMessage toHttpMessage(
158173
protected HttpRequest toHttpRequest(
159174
EvaluationContext context, Object input, Map<String, Object> options) {
160175
return switch (input) {
161-
case null -> throw new NullPointerException(name() + ": requires a request/response input");
162176
// default to http request
163177
case OperationExt op -> new HttpRequest(AsciiDocContext.from(context), op, options);
164178
case HttpRequest msg -> msg;
179+
case null -> throw new NullPointerException(name() + ": requires a request/response input");
165180
default ->
166181
throw new ClassCastException(
167182
name() + ": requires a request/response input: " + input.getClass());

0 commit comments

Comments
 (0)