Skip to content

Commit

Permalink
refactor(core): replace string concatenation with usage of JsonNode i…
Browse files Browse the repository at this point in the history
…n ExampleJsonGenerator (#394)

* Refactored ExampleJsonGenerator.java internal methods to return ObjectNode objects

* Removed unnecessary files

Removed unnecessary files

* Import required dependencies

* fix(core): replace star imports with single class imports

---------

Co-authored-by: David Müller <[email protected]>
  • Loading branch information
dipeshsingh253 and sam0r040 authored Oct 20, 2023
1 parent ce61c2d commit fd05041
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 39 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ springwolf-plugins/springwolf-sqs-plugin/build/
.idea/
/gradle.properties

# Eclipse IDE
.classpath
.project
.settings/
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
package io.github.stavshamir.springwolf.schemas.example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.RawValue;
import io.swagger.v3.core.util.Json;
Expand All @@ -26,25 +32,25 @@
public class ExampleJsonGenerator implements ExampleGenerator {

private static final ObjectMapper objectMapper = Json.mapper();
private static final String DEFAULT_NUMBER_EXAMPLE = "1.1";
private static final String DEFAULT_INTEGER_EXAMPLE = "0";
private static final String DEFAULT_BOOLEAN_EXAMPLE = "true";
private static final String DEFAULT_DATE_EXAMPLE = "\"2015-07-20\"";
private static final String DEFAULT_DATE_TIME_EXAMPLE = "\"2015-07-20T15:49:04-07:00\"";
private static final String DEFAULT_PASSWORD_EXAMPLE = "\"string-password\"";
private static final String DEFAULT_BYTE_EXAMPLE = "\"YmFzZTY0LWV4YW1wbGU=\"";
private static final Double DEFAULT_NUMBER_EXAMPLE = 1.1;
private static final Integer DEFAULT_INTEGER_EXAMPLE = 0;
private static final BooleanNode DEFAULT_BOOLEAN_EXAMPLE = BooleanNode.TRUE;
private static final String DEFAULT_DATE_EXAMPLE = "2015-07-20";
private static final String DEFAULT_DATE_TIME_EXAMPLE = "2015-07-20T15:49:04-07:00";
private static final String DEFAULT_PASSWORD_EXAMPLE = "string-password";
private static final String DEFAULT_BYTE_EXAMPLE = "YmFzZTY0LWV4YW1wbGU=";
private static final String DEFAULT_BINARY_EXAMPLE =
"\"0111010001100101011100110111010000101101011000100110100101101110011000010110010001111001\"";
private static final String DEFAULT_STRING_EXAMPLE = "\"string\"";
private static final String DEFAULT_EMAIL_EXAMPLE = "\"[email protected]\"";
private static final String DEFAULT_UUID_EXAMPLE = "\"3fa85f64-5717-4562-b3fc-2c963f66afa6\"";
"0111010001100101011100110111010000101101011000100110100101101110011000010110010001111001";
private static final String DEFAULT_STRING_EXAMPLE = "string";
private static final String DEFAULT_EMAIL_EXAMPLE = "[email protected]";
private static final String DEFAULT_UUID_EXAMPLE = "3fa85f64-5717-4562-b3fc-2c963f66afa6";

private static String DEFAULT_UNKNOWN_SCHEMA_EXAMPLE(String type) {
return "\"unknown schema type: " + type + "\"";
return "unknown schema type: " + type;
}

private static String DEFAULT_UNKNOWN_SCHEMA_STRING_EXAMPLE(String format) {
return "\"unknown string schema format: " + format + "\"";
return "unknown string schema format: " + format;
}

@Override
Expand All @@ -59,11 +65,11 @@ public Object fromSchema(Schema schema, Map<String, Schema> definitions) {
}

static String buildSchema(Schema schema, Map<String, Schema> definitions) {
return buildSchemaInternal(schema, definitions, new HashSet<>());
return buildSchemaInternal(schema, definitions, new HashSet<>()).toString();
}

private static String buildSchemaInternal(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
String exampleValue = ExampleJsonGenerator.getExampleValueFromSchemaAnnotation(schema);
private static JsonNode buildSchemaInternal(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
JsonNode exampleValue = ExampleJsonGenerator.getExampleValueFromSchemaAnnotation(schema);
if (exampleValue != null) {
return exampleValue;
}
Expand All @@ -80,29 +86,39 @@ private static String buildSchemaInternal(Schema schema, Map<String, Schema> def

String type = schema.getType();
return switch (type) {
case "array" -> ExampleJsonGenerator.handleArraySchema(schema, definitions, visited);
case "array" -> ExampleJsonGenerator.handleArraySchema(schema, definitions, visited); // Handle array schema
case "boolean" -> DEFAULT_BOOLEAN_EXAMPLE;
case "integer" -> DEFAULT_INTEGER_EXAMPLE;
case "number" -> DEFAULT_NUMBER_EXAMPLE;
case "object" -> ExampleJsonGenerator.handleObject(schema, definitions, visited);
case "string" -> ExampleJsonGenerator.handleStringSchema(schema);
default -> DEFAULT_UNKNOWN_SCHEMA_EXAMPLE(type);
case "integer" -> new IntNode(DEFAULT_INTEGER_EXAMPLE);
case "number" -> new DoubleNode(DEFAULT_NUMBER_EXAMPLE);
case "object" -> ExampleJsonGenerator.handleObject(schema, definitions, visited); // Handle object schema
case "string" -> JsonNodeFactory.instance.textNode(handleStringSchema(schema)); // Handle string schema
default -> JsonNodeFactory.instance.textNode(DEFAULT_UNKNOWN_SCHEMA_EXAMPLE(type));
};
}

private static String getExampleValueFromSchemaAnnotation(Schema schema) {
private static JsonNode getExampleValueFromSchemaAnnotation(Schema schema) {
Object exampleValue = schema.getExample();

// schema is a map of properties from a nested object, whose example cannot be inferred
if (exampleValue == null) {
return null;
}

// Create an ObjectNode to hold the example JSON
JsonNode exampleNode = objectMapper.createObjectNode();

// Handle special types (i.e. map) with custom @Schema annotation and specified example value
Object additionalProperties = schema.getAdditionalProperties();
if (additionalProperties instanceof StringSchema) {
StringSchema additionalPropertiesSchema = (StringSchema) additionalProperties;
Object exampleValueString = additionalPropertiesSchema.getExample();
if (exampleValueString != null) {
return (String) exampleValueString;
try {
exampleNode = objectMapper.readTree(exampleValueString.toString());
} catch (JsonProcessingException ex) {
log.debug("Unable to convert example to JSON: %s".formatted(exampleValue.toString()), ex);
}
return exampleNode;
}
}

Expand All @@ -112,28 +128,43 @@ private static String getExampleValueFromSchemaAnnotation(Schema schema) {
}

// exampleValue is represented in their native type
if (exampleValue instanceof Boolean || exampleValue instanceof Number) {
return exampleValue.toString();
if (exampleValue instanceof Boolean) {
return (Boolean) exampleValue ? BooleanNode.TRUE : BooleanNode.FALSE;
} else if (exampleValue instanceof Number) {
double doubleValue = ((Number) exampleValue).doubleValue();

// Check if it's an integer (whole number)
if (doubleValue == (int) doubleValue) {
return new IntNode((int) doubleValue);
}

return new DoubleNode(doubleValue);
}

try {
// exampleValue (i.e. OffsetDateTime) is represented as string
return objectMapper.writeValueAsString(exampleValue.toString());
} catch (JsonProcessingException ex) {
log.debug("Unable to convert example to string: %s".formatted(exampleValue.toString()), ex);
exampleNode = JsonNodeFactory.instance.textNode(exampleValue.toString());
} catch (IllegalArgumentException ex) {
log.debug("Unable to convert example to JSON: %s".formatted(exampleValue.toString()), ex);
}
return "\"\"";

return exampleNode;
}

private static String handleArraySchema(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
return Arrays.asList(buildSchemaInternal(schema.getItems(), definitions, visited))
.toString();
private static ArrayNode handleArraySchema(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();

List<JsonNode> list = Arrays.asList(buildSchemaInternal(schema.getItems(), definitions, visited));

for (JsonNode node : list) arrayNode.add(node);

return arrayNode;
}

private static String handleStringSchema(Schema schema) {
String firstEnumValue = getFirstEnumValue(schema);
if (firstEnumValue != null) {
return "\"" + firstEnumValue + "\"";
return firstEnumValue;
}

String format = schema.getFormat();
Expand Down Expand Up @@ -165,13 +196,14 @@ private static String getFirstEnumValue(Schema schema) {
return null;
}

private static String handleObject(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {
private static JsonNode handleObject(Schema schema, Map<String, Schema> definitions, Set<Schema> visited) {

Map<String, Schema> properties = schema.getProperties();
if (properties != null) {

if (!visited.contains(schema)) {
visited.add(schema);
String example = handleObjectProperties(properties, definitions, visited);
ObjectNode example = handleObjectProperties(properties, definitions, visited);
visited.remove(schema);

return example;
Expand All @@ -193,11 +225,12 @@ private static String handleObject(Schema schema, Map<String, Schema> definition
}

// i.e. A MapSchema is type=object, but has properties=null
return "{}";
return objectMapper.createObjectNode();
}

private static String handleObjectProperties(
private static ObjectNode handleObjectProperties(
Map<String, Schema> properties, Map<String, Schema> definitions, Set<Schema> visited) {

ObjectNode objectNode = objectMapper.createObjectNode();

properties.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
Expand All @@ -206,6 +239,6 @@ private static String handleObjectProperties(
objectNode.putRawValue(propertyKey, propertyRawValue);
});

return objectNode.toString();
return objectNode;
}
}

0 comments on commit fd05041

Please sign in to comment.