Skip to content

Commit 7ad2da4

Browse files
committed
pebble/ascii doc support: redo pebble function and filter
all them follow the given patter: `{{find/locate | mutator/filter | display}}` it creates a clear and common pattern for final users
1 parent 9842652 commit 7ad2da4

40 files changed

+3690
-88
lines changed

modules/jooby-openapi/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
<artifactId>pebble</artifactId>
6767
</dependency>
6868

69+
<dependency>
70+
<groupId>net.datafaker</groupId>
71+
<artifactId>datafaker</artifactId>
72+
<version>2.5.3</version>
73+
</dependency>
6974
<dependency>
7075
<groupId>commons-codec</groupId>
7176
<artifactId>commons-codec</artifactId>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@
1313

1414
public class EnumSchema extends StringSchema {
1515
@JsonIgnore private final Map<String, String> fields = new HashMap<>();
16+
@JsonIgnore private String summary;
1617

1718
public EnumSchema() {}
1819

20+
public String getSummary() {
21+
return summary;
22+
}
23+
24+
public void setSummary(String summary) {
25+
this.summary = summary;
26+
}
27+
1928
public void setDescription(String name, String description) {
2029
fields.put(name, description);
2130
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,11 @@ public OperationExt findOperationById(String operationId) {
244244

245245
public OperationExt findOperation(String method, String pattern) {
246246
Predicate<OperationExt> filter = op -> op.getPath().equals(pattern);
247-
if (method != null) {
248-
filter = filter.and(op -> op.getMethod().equals(method));
249-
}
247+
filter = filter.and(op -> op.getMethod().equals(method));
250248
return getOperations().stream()
251249
.filter(filter)
252250
.findFirst()
253251
.orElseThrow(
254-
() ->
255-
new IllegalArgumentException(
256-
"Operation not found: " + (method == null ? "" : method + " ") + pattern));
252+
() -> new IllegalArgumentException("Operation not found: " + method + " " + pattern));
257253
}
258254
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ private void document(Class typeName, Schema schema, ResolvedSchemaExt resolvedS
346346
enumSchema.setDescription(field, enumItemDesc);
347347
}
348348
}
349+
enumSchema.setSummary(enumDoc.getSummary());
349350
enumSchema.setDescription(enumDesc);
350351
}
351352
});
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.openapi.asciidoc;
7+
8+
import static java.util.Optional.ofNullable;
9+
10+
import java.nio.file.Path;
11+
import java.util.*;
12+
import java.util.function.BiConsumer;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
15+
16+
import com.fasterxml.jackson.core.JsonProcessingException;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
19+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
20+
import io.jooby.SneakyThrows;
21+
import io.jooby.internal.openapi.OpenAPIExt;
22+
import io.pebbletemplates.pebble.PebbleEngine;
23+
import io.pebbletemplates.pebble.extension.AbstractExtension;
24+
import io.pebbletemplates.pebble.extension.Filter;
25+
import io.pebbletemplates.pebble.extension.Function;
26+
import io.pebbletemplates.pebble.lexer.Syntax;
27+
import io.pebbletemplates.pebble.template.EvaluationContext;
28+
import io.swagger.v3.oas.models.OpenAPI;
29+
import io.swagger.v3.oas.models.media.Schema;
30+
31+
public class AsciiDocContext {
32+
public static final BiConsumer<String, Schema<?>> NOOP = (name, schema) -> {};
33+
public static final Schema EMPTY_SCHEMA = new Schema<>();
34+
35+
private ClassLoader classLoader;
36+
37+
private ObjectMapper json;
38+
39+
private ObjectMapper yamlOpenApi;
40+
41+
private ObjectMapper yamlOutput;
42+
43+
private PebbleEngine engine;
44+
45+
private OpenAPIExt openapi;
46+
47+
private Path baseDir;
48+
49+
private Path outputDir;
50+
51+
private final AutoDataFakerMapper faker = new AutoDataFakerMapper();
52+
53+
private final Map<Schema<?>, Map<String, Object>> examples = new HashMap<>();
54+
55+
public AsciiDocContext(
56+
ClassLoader classLoader,
57+
ObjectMapper json,
58+
ObjectMapper yaml,
59+
OpenAPIExt openapi,
60+
Path baseDir,
61+
Path outputDir) {
62+
this.classLoader = classLoader;
63+
this.json = json;
64+
this.yamlOpenApi = yaml;
65+
this.yamlOutput = newYamlOutput();
66+
this.engine = createEngine(json, openapi, this);
67+
this.openapi = openapi;
68+
this.baseDir = baseDir;
69+
this.outputDir = outputDir;
70+
}
71+
72+
private ObjectMapper newYamlOutput() {
73+
var factory = new YAMLFactory();
74+
factory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES);
75+
factory.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
76+
return new ObjectMapper(factory);
77+
}
78+
79+
private static PebbleEngine createEngine(
80+
ObjectMapper json, OpenAPI openapi, AsciiDocContext context) {
81+
return new PebbleEngine.Builder()
82+
.autoEscaping(false)
83+
.extension(
84+
new AbstractExtension() {
85+
@Override
86+
public Map<String, Object> getGlobalVariables() {
87+
Map<String, Object> openapiRoot = json.convertValue(openapi, Map.class);
88+
openapiRoot.put("openapi", openapi);
89+
openapiRoot.put("_asciidocContext", context);
90+
return openapiRoot;
91+
}
92+
93+
@Override
94+
public Map<String, Function> getFunctions() {
95+
return Lookup.lookup();
96+
}
97+
98+
@Override
99+
public Map<String, Filter> getFilters() {
100+
Map<String, Filter> filters = new HashMap<>();
101+
filters.putAll(Mutator.seek());
102+
filters.putAll(Display.display());
103+
return filters;
104+
}
105+
})
106+
.syntax(new Syntax.Builder().setEnableNewLineTrimming(false).build())
107+
.build();
108+
}
109+
110+
public String schemaType(Schema<?> schema) {
111+
var resolved = resolveSchema(schema);
112+
return Optional.ofNullable(resolved.getFormat()).orElse(resolved.getType());
113+
}
114+
115+
public Schema<?> resolveSchema(Schema<?> schema) {
116+
if (schema == EMPTY_SCHEMA) {
117+
return schema;
118+
}
119+
if (schema.get$ref() != null) {
120+
return resolveSchemaInternal(schema.get$ref())
121+
.orElseThrow(() -> new NoSuchElementException("Schema not found: " + schema.get$ref()));
122+
}
123+
return schema;
124+
}
125+
126+
public Map<String, Object> schemaProperties(Schema<?> schema) {
127+
return traverse(schema, NOOP);
128+
}
129+
130+
@SuppressWarnings("rawtypes")
131+
public Schema<?> reduceSchema(Schema<?> schema) {
132+
var truncated = emptySchema(schema);
133+
var properties = new LinkedHashMap<String, Schema>();
134+
traverse(
135+
schema,
136+
(name, value) -> {
137+
var type = value.getType();
138+
if ("object".equals(type)) {
139+
var object = new Schema<>();
140+
object.setType(type);
141+
properties.put(name, object);
142+
} else if ("array".equals(type)) {
143+
var array = new Schema<>();
144+
array.setType(type);
145+
array.setItems(new Schema<>());
146+
properties.put(name, array);
147+
} else {
148+
properties.put(name, value);
149+
}
150+
});
151+
truncated.setProperties(properties);
152+
return truncated;
153+
}
154+
155+
public Schema<?> emptySchema(Schema<?> schema) {
156+
var empty = new Schema<>();
157+
empty.setType(schema.getType());
158+
empty.setName(schema.getName());
159+
return empty;
160+
}
161+
162+
public Map<String, Object> schemaExample(Schema<?> schema) {
163+
return examples.computeIfAbsent(
164+
schema,
165+
s ->
166+
traverse(
167+
s,
168+
(parent, property) -> {
169+
var enumItems = property.getEnum();
170+
if (enumItems == null || enumItems.isEmpty()) {
171+
var type = schemaType(property);
172+
var gen = faker.getGenerator(parent.getName(), property.getName(), type, type);
173+
return gen.get();
174+
} else {
175+
return enumItems.get(new Random().nextInt(enumItems.size())).toString();
176+
}
177+
},
178+
NOOP,
179+
NOOP));
180+
}
181+
182+
public void traverseSchema(Schema<?> schema, BiConsumer<String, Schema<?>> consumer) {
183+
traverse(schema, consumer);
184+
}
185+
186+
public void traverseGraph(Schema<?> schema, BiConsumer<String, Schema<?>> consumer) {
187+
traverse(schema, consumer, consumer);
188+
}
189+
190+
private Map<String, Object> traverse(Schema<?> schema, BiConsumer<String, Schema<?>> consumer) {
191+
return traverse(schema, consumer, NOOP);
192+
}
193+
194+
private Map<String, Object> traverse(
195+
Schema<?> schema,
196+
BiConsumer<String, Schema<?>> consumer,
197+
BiConsumer<String, Schema<?>> inner) {
198+
return traverse(schema, (parent, property) -> schemaType(property), consumer, inner);
199+
}
200+
201+
private Map<String, Object> traverse(
202+
Schema<?> schema,
203+
SneakyThrows.Function2<Schema<?>, Schema<?>, String> valueMapper,
204+
BiConsumer<String, Schema<?>> consumer,
205+
BiConsumer<String, Schema<?>> inner) {
206+
var resolved = resolveSchema(schema);
207+
var properties = resolved.getProperties();
208+
if (properties != null) {
209+
Map<String, Object> result = new LinkedHashMap<>();
210+
properties.forEach(
211+
(name, value) -> {
212+
value = resolveSchema(value);
213+
consumer.accept(name, value);
214+
if (value.getType().equals("object")) {
215+
result.put(name, traverse(value, valueMapper, inner, inner));
216+
} else if (value.getType().equals("array")) {
217+
var array =
218+
ofNullable(value.getItems())
219+
.map(items -> traverse(items, valueMapper, inner, inner))
220+
.map(List::of)
221+
.orElse(List.of());
222+
result.put(name, array);
223+
} else {
224+
result.put(name, valueMapper.apply(resolved, value));
225+
}
226+
});
227+
return result;
228+
}
229+
return Map.of();
230+
}
231+
232+
public Schema<?> resolveSchema(String path) {
233+
var segments = path.split("\\.");
234+
var schema =
235+
resolveSchemaInternal(segments[0])
236+
.orElseThrow(() -> new NoSuchElementException("Schema not found: " + path));
237+
238+
for (int i = 1; i < segments.length; i++) {
239+
Schema<?> inner = (Schema<?>) schema.getProperties().get(segments[i]);
240+
if (inner == null) {
241+
throw new IllegalArgumentException(
242+
"Property not found: " + Stream.of(segments).limit(i).collect(Collectors.joining(".")));
243+
}
244+
if (inner.get$ref() != null) {
245+
inner =
246+
resolveSchemaInternal(inner.get$ref())
247+
.orElseThrow(() -> new NoSuchElementException("Schema not found: " + path));
248+
}
249+
schema = inner;
250+
}
251+
252+
return schema;
253+
}
254+
255+
private Optional<Schema<?>> resolveSchemaInternal(String name) {
256+
var components = openapi.getComponents();
257+
if (components == null || components.getSchemas() == null) {
258+
throw new NoSuchElementException("No schema found");
259+
}
260+
if (name.startsWith("#/components/schemas/")) {
261+
name = name.substring("#/components/schemas/".length());
262+
}
263+
return Optional.ofNullable((Schema<?>) components.getSchemas().get(name));
264+
}
265+
266+
public PebbleEngine getEngine() {
267+
return engine;
268+
}
269+
270+
public Path getBaseDir() {
271+
return baseDir;
272+
}
273+
274+
public Path getOutputDir() {
275+
return outputDir;
276+
}
277+
278+
public ClassLoader getClassLoader() {
279+
return classLoader;
280+
}
281+
282+
public String toJson(Object input, boolean pretty) {
283+
try {
284+
var writer = pretty ? json.writer().withDefaultPrettyPrinter() : json.writer();
285+
return writer.writeValueAsString(input);
286+
} catch (JsonProcessingException e) {
287+
throw SneakyThrows.propagate(e);
288+
}
289+
}
290+
291+
public String toYaml(Object input) {
292+
try {
293+
return cleanYaml(
294+
input instanceof Map
295+
? yamlOutput.writeValueAsString(input)
296+
: yamlOpenApi.writeValueAsString(input));
297+
} catch (JsonProcessingException e) {
298+
throw SneakyThrows.propagate(e);
299+
}
300+
}
301+
302+
private String cleanYaml(String value) {
303+
return value.trim();
304+
}
305+
306+
public ObjectMapper getJson() {
307+
return json;
308+
}
309+
310+
public ObjectMapper getYaml() {
311+
return yamlOpenApi;
312+
}
313+
314+
public OpenAPIExt getOpenApi() {
315+
return openapi;
316+
}
317+
318+
public static AsciiDocContext from(EvaluationContext context) {
319+
return (AsciiDocContext) context.getVariable("_asciidocContext");
320+
}
321+
}

0 commit comments

Comments
 (0)