From fd050417700232e530cc095f53b99fa53f179186 Mon Sep 17 00:00:00 2001 From: Dipesh Singh Date: Fri, 20 Oct 2023 19:23:45 +0530 Subject: [PATCH] refactor(core): replace string concatenation with usage of JsonNode in ExampleJsonGenerator (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .gitignore | 4 + .../schemas/example/ExampleJsonGenerator.java | 111 ++++++++++++------ 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 580a092a6..b3defe718 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ springwolf-plugins/springwolf-sqs-plugin/build/ .idea/ /gradle.properties +# Eclipse IDE +.classpath +.project +.settings/ diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleJsonGenerator.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleJsonGenerator.java index 528172667..38e44ee2a 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleJsonGenerator.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleJsonGenerator.java @@ -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; @@ -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 = "\"example@example.com\""; - 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 = "example@example.com"; + 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 @@ -59,11 +65,11 @@ public Object fromSchema(Schema schema, Map definitions) { } static String buildSchema(Schema schema, Map definitions) { - return buildSchemaInternal(schema, definitions, new HashSet<>()); + return buildSchemaInternal(schema, definitions, new HashSet<>()).toString(); } - private static String buildSchemaInternal(Schema schema, Map definitions, Set visited) { - String exampleValue = ExampleJsonGenerator.getExampleValueFromSchemaAnnotation(schema); + private static JsonNode buildSchemaInternal(Schema schema, Map definitions, Set visited) { + JsonNode exampleValue = ExampleJsonGenerator.getExampleValueFromSchemaAnnotation(schema); if (exampleValue != null) { return exampleValue; } @@ -80,29 +86,39 @@ private static String buildSchemaInternal(Schema schema, Map 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; } } @@ -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 definitions, Set visited) { - return Arrays.asList(buildSchemaInternal(schema.getItems(), definitions, visited)) - .toString(); + private static ArrayNode handleArraySchema(Schema schema, Map definitions, Set visited) { + ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode(); + + List 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(); @@ -165,13 +196,14 @@ private static String getFirstEnumValue(Schema schema) { return null; } - private static String handleObject(Schema schema, Map definitions, Set visited) { + private static JsonNode handleObject(Schema schema, Map definitions, Set visited) { + Map 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; @@ -193,11 +225,12 @@ private static String handleObject(Schema schema, Map 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 properties, Map definitions, Set visited) { + ObjectNode objectNode = objectMapper.createObjectNode(); properties.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { @@ -206,6 +239,6 @@ private static String handleObjectProperties( objectNode.putRawValue(propertyKey, propertyRawValue); }); - return objectNode.toString(); + return objectNode; } }