Skip to content

Commit

Permalink
feat(core): handle example root schema lazily
Browse files Browse the repository at this point in the history
Co-authored-by: David Müller <david.mueller@codecentric.de>
  • Loading branch information
timonback and sam0r040 committed Sep 13, 2024
1 parent 6e18922 commit cce0593
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@ public R fromSchema(Schema schema, Map<String, Schema> definitions) {
exampleValueGenerator.initialize();

try {
String schemaName = exampleValueGenerator
.lookupSchemaName(schema)
.orElseThrow(() ->
new ExampleGeneratingException("There is no name set for Schema: " + schema.toString()));
Optional<String> schemaName = exampleValueGenerator.lookupSchemaName(schema);

T generatedExample = buildExample(schemaName, schema, definitions, new HashSet<>())
.orElseThrow(() -> new ExampleGeneratingException("Something went wrong"));
Expand All @@ -71,7 +68,8 @@ public R fromSchema(Schema schema, Map<String, Schema> definitions) {
return null;
}

private Optional<T> buildExample(String name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
private Optional<T> buildExample(
Optional<String> name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
log.debug("Building example for schema {}", schema);

Optional<T> exampleValue = getExampleFromSchemaAnnotation(name, schema);
Expand All @@ -88,12 +86,13 @@ private Optional<T> buildExample(String name, Schema schema, Map<String, Schema>
return example;
}

private Optional<T> getExampleFromSchemaAnnotation(String fieldName, Schema schema) {
private Optional<T> getExampleFromSchemaAnnotation(Optional<String> fieldName, Schema schema) {
return getExampleValueFromSchemaAnnotation(fieldName, schema, schema.getExample())
.or(() -> getExampleValueFromSchemaAnnotation(fieldName, schema, schema.getDefault()));
}

private Optional<T> getExampleValueFromSchemaAnnotation(String fieldName, Schema schema, Object exampleValue) {
private Optional<T> getExampleValueFromSchemaAnnotation(
Optional<String> fieldName, Schema schema, Object exampleValue) {
// schema is a map of properties from a nested object, whose example cannot be inferred
if (exampleValue == null) {
return Optional.empty();
Expand Down Expand Up @@ -159,7 +158,7 @@ private Optional<T> getExampleValueFromSchemaAnnotation(String fieldName, Schema
* The caller must ensure that the schema has not been visited before to avoid infinite recursion
*/
private Optional<T> buildExampleFromUnvisitedSchema(
String name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<Schema<?>> resolvedSchema = resolveSchemaFromRef(schema, definitions);
if (resolvedSchema.isPresent()) {
return buildExample(name, resolvedSchema.get(), definitions, visited);
Expand Down Expand Up @@ -192,7 +191,8 @@ private Optional<T> buildArrayExample(Schema schema, Map<String, Schema> definit
return exampleValueGenerator
.lookupSchemaName(arrayItemSchema)
.or(() -> arrayName)
.flatMap(arrayItemName -> buildExample(arrayItemName, arrayItemSchema, definitions, visited))
.flatMap(arrayItemName ->
buildExample(Optional.of(arrayItemName), arrayItemSchema, definitions, visited))
.map(arrayItem -> exampleValueGenerator.createArrayExample(arrayName, arrayItem));
}

Expand Down Expand Up @@ -231,7 +231,7 @@ private String getFirstEnumValue(Schema schema) {
}

private Optional<T> buildFromComposedSchema(
String name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
final List<Schema> schemasAllOf = schema.getAllOf();
final List<Schema> schemasAnyOf = schema.getAnyOf();
final List<Schema> schemasOneOf = schema.getOneOf();
Expand All @@ -247,7 +247,7 @@ private Optional<T> buildFromComposedSchema(
}

private Optional<T> buildFromObjectSchema(
String name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name, Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
final Optional<T> exampleValue;

final Map<String, Schema> properties = schema.getProperties();
Expand All @@ -264,7 +264,7 @@ private Optional<T> buildFromObjectSchema(
}

private Optional<T> buildMapExample(
String name, Schema additionalProperties, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name, Schema additionalProperties, Map<String, Schema> definitions, Set<Schema> visited) {
T object = exampleValueGenerator.startObject(name);
Map<String, Schema> mapProperties = Map.of(DEFAULT_MAP_KEY_EXAMPLE, additionalProperties);
exampleValueGenerator.addPropertyExamples(
Expand All @@ -275,7 +275,10 @@ private Optional<T> buildMapExample(
}

private Optional<T> buildFromObjectSchemaWithProperties(
String name, Map<String, Schema> properties, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name,
Map<String, Schema> properties,
Map<String, Schema> definitions,
Set<Schema> visited) {
T object = exampleValueGenerator.startObject(name);
exampleValueGenerator.addPropertyExamples(
object, buildPropertyExampleListFromSchema(properties, definitions, visited));
Expand All @@ -285,7 +288,7 @@ private Optional<T> buildFromObjectSchemaWithProperties(
}

private Optional<T> buildFromObjectSchemaWithAllOf(
String name, List<Schema> schemasAllOf, Map<String, Schema> definitions, Set<Schema> visited) {
Optional<String> name, List<Schema> schemasAllOf, Map<String, Schema> definitions, Set<Schema> visited) {
T object = exampleValueGenerator.startObject(name);
exampleValueGenerator.addPropertyExamples(
object, buildPropertyExampleListFromSchemas(schemasAllOf, definitions, visited));
Expand All @@ -305,7 +308,7 @@ private List<PropertyExample<T>> buildPropertyExampleListFromSchema(
.orElse(propertySchema.getKey());

Optional<T> propertyValue =
buildExample(propertyKey, propertySchema.getValue(), definitions, visited);
buildExample(Optional.of(propertyKey), propertySchema.getValue(), definitions, visited);

return propertyValue
.map(optionalElem -> new PropertyExample<>(propertyKey, optionalElem))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ default void initialize() {}

Optional<T> createUnknownSchemaStringFormatExample(String schemaFormat);

T startObject(String name);
T startObject(Optional<String> name);

default void endObject() {}

Expand All @@ -54,5 +54,5 @@ default void endObject() {}

T createRaw(Object exampleValueString);

T getExampleOrNull(String fieldName, Schema schema, Object example);
T getExampleOrNull(Optional<String> fieldName, Schema schema, Object example);
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public JsonNode createRaw(Object exampleValue) {
}

@Override
public JsonNode getExampleOrNull(String fieldName, Schema schema, Object example) {
public JsonNode getExampleOrNull(Optional<String> fieldName, Schema schema, Object example) {
if (example instanceof JsonNode) {
return (JsonNode) example;
}
Expand All @@ -106,7 +106,7 @@ public JsonNode getExampleOrNull(String fieldName, Schema schema, Object example
}

@Override
public JsonNode startObject(String name) {
public JsonNode startObject(Optional<String> name) {
return objectMapper.createObjectNode();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,9 @@ public Optional<Node> createBooleanExample(Boolean value, Schema schema) {
}

@Override
public Element startObject(String name) {
if (name == null) {
throw new IllegalArgumentException("Object name must not be empty");
}

return nodeStack.push(document.createElement(name));
public Element startObject(Optional<String> name) {
return nodeStack.push(document.createElement(name.orElseThrow(
() -> new SchemaWalker.ExampleGeneratingException("There is no name set for Schema"))));
}

@Override
Expand Down Expand Up @@ -198,7 +195,7 @@ public Node createRaw(Object exampleValue) {
}

@Override
public Node getExampleOrNull(String fieldName, Schema schema, Object example) {
public Node getExampleOrNull(Optional<String> fieldName, Schema schema, Object example) {
String name = getCacheKey(schema);

if (example instanceof Node) {
Expand All @@ -207,7 +204,10 @@ public Node getExampleOrNull(String fieldName, Schema schema, Object example) {

if (exampleCache.containsKey(name)) {
Node oldElement = exampleCache.get(name);
Node newElement = modifyElementFromCacheIfNeeded(oldElement, fieldName);
Node newElement = modifyElementFromCacheIfNeeded(
oldElement,
fieldName.orElseThrow(
() -> new SchemaWalker.ExampleGeneratingException("There is no name set for Schema")));
return this.document.importNode(newElement, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public Optional<JsonNode> createBooleanExample(Boolean value, Schema schema) {
}

@Override
public JsonNode startObject(String name) {
public JsonNode startObject(Optional<String> name) {
return this.exampleJsonValueGenerator.startObject(name);
}

Expand Down Expand Up @@ -116,7 +116,7 @@ public JsonNode createRaw(Object exampleValueString) {
}

@Override
public JsonNode getExampleOrNull(String fieldName, Schema schema, Object example) {
public JsonNode getExampleOrNull(Optional<String> fieldName, Schema schema, Object example) {
return this.exampleJsonValueGenerator.getExampleOrNull(fieldName, schema, example);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;

Expand All @@ -33,11 +35,13 @@ void cacheShouldResolveBySchemaName() {
.get();
generator.prepareForSerialization(schema1, example1);

Node cachedExample1 = generator.getExampleOrNull("fieldName1", schema1, "does-not-matter-for-test-1");
Node cachedExample1 =
generator.getExampleOrNull(Optional.of("fieldName1"), schema1, "does-not-matter-for-test-1");

// when
generator.initialize();
Node exampleFromCache = generator.getExampleOrNull("fieldName2", schema2, "does-not-matter-for-test-2");
Node exampleFromCache =
generator.getExampleOrNull(Optional.of("fieldName2"), schema2, "does-not-matter-for-test-2");

// then
assertThat(exampleFromCache).isNotEqualTo(cachedExample1);
Expand All @@ -62,11 +66,13 @@ void cacheShouldResolveBySchemaNameAndRenameToWrappingField() {
Node example1 = generator.createRaw("<xml><value>aValue</value></xml>");
generator.prepareForSerialization(schema1, example1);

Node cachedExample1 = generator.getExampleOrNull("fieldName1", schema1, "does-not-matter-for-test-1");
Node cachedExample1 =
generator.getExampleOrNull(Optional.of("fieldName1"), schema1, "does-not-matter-for-test-1");

// when
generator.initialize();
Node exampleFromCache = generator.getExampleOrNull("fieldName2", schema2, "does-not-matter-for-test-2");
Node exampleFromCache =
generator.getExampleOrNull(Optional.of("fieldName2"), schema2, "does-not-matter-for-test-2");

// then
assertThat(((Element) cachedExample1).getTagName()).isEqualTo("fieldName1");
Expand All @@ -90,7 +96,7 @@ void cacheShouldStoreExampleBySchemaName() {
generator.prepareForSerialization(schema1, example1);

generator.initialize();
Node exampleFromCache = generator.getExampleOrNull("fieldName", schema2, "example-string");
Node exampleFromCache = generator.getExampleOrNull(Optional.of("fieldName"), schema2, "example-string");

assertThat(exampleFromCache).isNull();
}
Expand Down

0 comments on commit cce0593

Please sign in to comment.