From b57bfd2f4fdcc842b0ae5262e6fe9a669aafe35d Mon Sep 17 00:00:00 2001 From: Timon Back Date: Fri, 12 Jul 2024 16:10:44 +0200 Subject: [PATCH] feat(core): handle map schemas (#839) * feat(core): handle map schemas * fix(core): handle examples in xml schemas --- .../examples/walkers/DefaultSchemaWalker.java | 25 +++++++++++++++---- .../walkers/xml/ExampleXmlValueGenerator.java | 6 ++++- .../DefaultXmlComponentsServiceTest.java | 2 +- ...efaultSchemaWalkerJsonIntegrationTest.java | 15 +++++++++++ ...DefaultSchemaWalkerXmlIntegrationTest.java | 12 +++++++++ ...efaultSchemaWalkerYamlIntegrationTest.java | 14 +++++++++++ .../xml/ExampleXmlValueGeneratorTest.java | 14 +++++++++++ .../schemas/complex-definitions.json | 12 +++++++-- .../schemas/documented-definitions.json | 4 ++- ...mplex-definitions-with-attributes-xml.json | 6 ++--- .../schemas/xml/complex-definitions-xml.json | 6 ++--- .../xml/documented-definitions-xml.json | 6 ++--- .../yaml/complex-definitions-yaml.json | 6 ++--- .../yaml/documented-definitions-yaml.json | 4 +-- 14 files changed, 108 insertions(+), 24 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalker.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalker.java index 94a42aca0..9ca26fd61 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalker.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalker.java @@ -2,6 +2,7 @@ package io.github.springwolf.core.asyncapi.components.examples.walkers; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.media.StringSchema; import lombok.RequiredArgsConstructor; @@ -28,6 +29,7 @@ public class DefaultSchemaWalker implements SchemaWalker { Boolean DEFAULT_BOOLEAN_EXAMPLE = true; + String DEFAULT_MAP_KEY_EXAMPLE = "key"; String DEFAULT_STRING_EXAMPLE = "string"; Integer DEFAULT_INTEGER_EXAMPLE = 0; Double DEFAULT_NUMBER_EXAMPLE = 1.1; @@ -215,10 +217,11 @@ private Optional buildFromObjectSchema( final Optional exampleValue; - Map properties = schema.getProperties(); - List schemasAllOf = schema.getAllOf(); - List schemasAnyOf = schema.getAnyOf(); - List schemasOneOf = schema.getOneOf(); + final Map properties = schema.getProperties(); + final Object additionalProperties = schema.getAdditionalProperties(); + final List schemasAllOf = schema.getAllOf(); + final List schemasAnyOf = schema.getAnyOf(); + final List schemasOneOf = schema.getOneOf(); if (properties != null) { exampleValue = buildFromObjectSchemaWithProperties(name, properties, definitions, visited); } else if (!CollectionUtils.isEmpty(schemasAllOf)) { @@ -227,8 +230,9 @@ private Optional buildFromObjectSchema( exampleValue = buildExample(name, schemasAnyOf.get(0), definitions, visited); } else if (!CollectionUtils.isEmpty(schemasOneOf)) { exampleValue = buildExample(name, schemasOneOf.get(0), definitions, visited); + } else if (schema instanceof MapSchema && additionalProperties instanceof Schema) { + exampleValue = buildMapExample(name, (Schema) additionalProperties, definitions, visited); } else { - // i.e. A MapSchema is type=object, but has properties=null exampleValue = exampleValueGenerator.createEmptyObjectExample(); } @@ -237,6 +241,17 @@ private Optional buildFromObjectSchema( return exampleValue; } + private Optional buildMapExample( + String name, Schema additionalProperties, Map definitions, Set visited) { + T object = exampleValueGenerator.startObject(name); + Map mapProperties = Map.of(DEFAULT_MAP_KEY_EXAMPLE, additionalProperties); + exampleValueGenerator.addPropertyExamples( + object, buildPropertyExampleListFromSchema(mapProperties, definitions, visited)); + exampleValueGenerator.endObject(); + + return Optional.of(object); + } + private Optional buildFromObjectSchemaWithProperties( String name, Map properties, Map definitions, Set visited) { T object = exampleValueGenerator.startObject(name); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGenerator.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGenerator.java index 5cd4f503d..41a46b4d7 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGenerator.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGenerator.java @@ -11,6 +11,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.Text; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; @@ -19,6 +20,7 @@ import javax.xml.transform.TransformerException; import java.io.IOException; +import java.io.StringReader; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -223,7 +225,9 @@ private Document createDocument() throws ParserConfigurationException { private Node readXmlString(String xmlString) { try { DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - return documentBuilder.parse(xmlString); + InputSource is = new InputSource(new StringReader(xmlString)); + Element element = documentBuilder.parse(is).getDocumentElement(); + return document.importNode(element, true); } catch (SAXException | IOException | ParserConfigurationException e) { log.info("Unable to convert example to XMl Node: {}", xmlString, e); } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/DefaultXmlComponentsServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/DefaultXmlComponentsServiceTest.java index 613342301..5e09dc81f 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/DefaultXmlComponentsServiceTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/DefaultXmlComponentsServiceTest.java @@ -160,7 +160,7 @@ private static class DocumentedSimpleFoo { @Schema(description = "List without example") private List ls_plain; - @Schema(description = "Map with example", example = "value1") + @Schema(description = "Map with example", example = "value1") private Map mss; @Schema(description = "Map without example") diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerJsonIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerJsonIntegrationTest.java index 60a94a8b8..0263d6441 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerJsonIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerJsonIntegrationTest.java @@ -13,6 +13,7 @@ import io.swagger.v3.oas.models.media.DateTimeSchema; import io.swagger.v3.oas.models.media.EmailSchema; import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.PasswordSchema; @@ -442,6 +443,20 @@ void object_with_allOf(TestInfo testInfo) throws JsonProcessingException { assertThat(actualString).isEqualTo("{\"allOfField\":{\"field1\":\"string\",\"field2\":1.1}}"); } + @Test + void object_with_map(TestInfo testInfo) throws JsonProcessingException { + MapSchema mapSchema = new MapSchema(); + mapSchema.setName(testInfo.getDisplayName()); + + Schema propertySchema = new StringSchema(); + mapSchema.setAdditionalProperties(propertySchema); + + JsonNode actual = jsonSchemaWalker.fromSchema(mapSchema, Map.of("Nested", propertySchema)); + String actualString = jsonMapper.writeValueAsString(actual); + + assertThat(actualString).isEqualTo("{\"key\":\"string\"}"); + } + @Test void schema_with_problematic_object_toString_example(TestInfo testInfo) throws JsonProcessingException { ObjectSchema schema = new ObjectSchema(); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerXmlIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerXmlIntegrationTest.java index d6fdaa569..8027fee1d 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerXmlIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerXmlIntegrationTest.java @@ -508,6 +508,18 @@ void object_with_allOf() { "string1.1"); } + @Test + void object_with_map() { + MapSchema mapSchema = new MapSchema(); + mapSchema.setName("object_with_map"); + + Schema propertySchema = new StringSchema(); + mapSchema.setAdditionalProperties(propertySchema); + + String actual = xmlSchemaWalker.fromSchema(mapSchema, Map.of("Nested", propertySchema)); + assertThat(actual).isEqualTo("string"); + } + @Test void schema_with_problematic_object_toString_example() { ObjectSchema schema = new ObjectSchema(); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerYamlIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerYamlIntegrationTest.java index e1037c6bb..477b91ea0 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerYamlIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/DefaultSchemaWalkerYamlIntegrationTest.java @@ -13,6 +13,7 @@ import io.swagger.v3.oas.models.media.DateTimeSchema; import io.swagger.v3.oas.models.media.EmailSchema; import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.PasswordSchema; @@ -490,6 +491,19 @@ void object_with_allOf(TestInfo testInfo) { """); } + @Test + void object_with_map(TestInfo testInfo) { + MapSchema mapSchema = new MapSchema(); + mapSchema.setName(testInfo.getDisplayName()); + + Schema propertySchema = new StringSchema(); + mapSchema.setAdditionalProperties(propertySchema); + + String actualString = jsonSchemaWalker.fromSchema(mapSchema, Map.of("Nested", propertySchema)); + + assertThat(actualString).isEqualTo("key: \"string\"\n"); + } + @Test void schema_with_problematic_object_toString_example(TestInfo testInfo) { ObjectSchema schema = new ObjectSchema(); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGeneratorTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGeneratorTest.java index 5bb3256ea..fde7cf045 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGeneratorTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/components/examples/walkers/xml/ExampleXmlValueGeneratorTest.java @@ -7,6 +7,7 @@ import org.w3c.dom.Node; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; class ExampleXmlValueGeneratorTest { @@ -63,4 +64,17 @@ void cacheShouldStoreExampleBySchemaName() { assertThat(exampleFromCache).isNull(); } + + @Test + void shouldCreateRawFromXmlString() { + // given + ExampleXmlValueGenerator generator = new ExampleXmlValueGenerator(new DefaultExampleXmlValueSerializer()); + generator.initialize(); + + // when + Node result = generator.createRaw("example"); + + // then + assertNotNull(result); + } } diff --git a/springwolf-core/src/test/resources/schemas/complex-definitions.json b/springwolf-core/src/test/resources/schemas/complex-definitions.json index 7adc584c9..5c5342761 100644 --- a/springwolf-core/src/test/resources/schemas/complex-definitions.json +++ b/springwolf-core/src/test/resources/schemas/complex-definitions.json @@ -40,7 +40,11 @@ "cyclic" : { } }, "nli" : [ 0 ], - "nmfm" : { }, + "nmfm" : { + "key" : { + "s" : "string" + } + }, "ns" : "string", "nsm" : [ { "s" : "string" @@ -94,7 +98,11 @@ "cyclic" : { } }, "nli" : [ 0 ], - "nmfm" : { }, + "nmfm" : { + "key" : { + "s" : "string" + } + }, "ns" : "string", "nsm" : [ { "s" : "string" diff --git a/springwolf-core/src/test/resources/schemas/documented-definitions.json b/springwolf-core/src/test/resources/schemas/documented-definitions.json index fba6fd565..0dfed7810 100644 --- a/springwolf-core/src/test/resources/schemas/documented-definitions.json +++ b/springwolf-core/src/test/resources/schemas/documented-definitions.json @@ -65,7 +65,9 @@ "mss" : { "key1" : "value1" }, - "mss_plain" : { }, + "mss_plain" : { + "key" : "string" + }, "s" : "s value" } ], "required" : [ "dt", "f", "s" ] diff --git a/springwolf-core/src/test/resources/schemas/xml/complex-definitions-with-attributes-xml.json b/springwolf-core/src/test/resources/schemas/xml/complex-definitions-with-attributes-xml.json index 148888e28..5e9e63547 100644 --- a/springwolf-core/src/test/resources/schemas/xml/complex-definitions-with-attributes-xml.json +++ b/springwolf-core/src/test/resources/schemas/xml/complex-definitions-with-attributes-xml.json @@ -28,7 +28,7 @@ "type" : "string" } }, - "examples" : [ "0string" ] + "examples" : [ "0stringstring" ] }, "io.github.springwolf.core.asyncapi.components.DefaultXmlComponentsServiceTest$ComplexAttributesFoo$Nested" : { "type" : "string", @@ -57,7 +57,7 @@ "uniqueItems" : true } }, - "examples" : [ "0string" ] + "examples" : [ "0stringstring" ] }, "io.github.springwolf.core.asyncapi.components.DefaultXmlComponentsServiceTest$ComplexAttributesFoo$Nested$MyClassWithAttribute" : { "type" : "string", @@ -71,4 +71,4 @@ }, "examples" : [ "string" ] } -} +} \ No newline at end of file diff --git a/springwolf-core/src/test/resources/schemas/xml/complex-definitions-xml.json b/springwolf-core/src/test/resources/schemas/xml/complex-definitions-xml.json index 05e79988e..ed0cc9a5c 100644 --- a/springwolf-core/src/test/resources/schemas/xml/complex-definitions-xml.json +++ b/springwolf-core/src/test/resources/schemas/xml/complex-definitions-xml.json @@ -28,7 +28,7 @@ "type" : "string" } }, - "examples" : [ "true1.1
2015-07-20T15:49:04-07:00
1.10YmFzZTY0LWV4YW1wbGU=0stringstring3fa85f64-5717-4562-b3fc-2c963f66afa6string
" ] + "examples" : [ "true1.1
2015-07-20T15:49:04-07:00
1.10YmFzZTY0LWV4YW1wbGU=0stringstringstring3fa85f64-5717-4562-b3fc-2c963f66afa6string
" ] }, "io.github.springwolf.core.asyncapi.components.DefaultXmlComponentsServiceTest$ComplexFoo$Nested" : { "type" : "string", @@ -68,7 +68,7 @@ "format" : "uuid" } }, - "examples" : [ "YmFzZTY0LWV4YW1wbGU=0stringstring3fa85f64-5717-4562-b3fc-2c963f66afa6" ] + "examples" : [ "YmFzZTY0LWV4YW1wbGU=0stringstringstring3fa85f64-5717-4562-b3fc-2c963f66afa6" ] }, "io.github.springwolf.core.asyncapi.components.DefaultXmlComponentsServiceTest$ComplexFoo$Nested$Cyclic" : { "type" : "string", @@ -88,4 +88,4 @@ }, "examples" : [ "string" ] } -} +} \ No newline at end of file diff --git a/springwolf-core/src/test/resources/schemas/xml/documented-definitions-xml.json b/springwolf-core/src/test/resources/schemas/xml/documented-definitions-xml.json index 7f31deb6f..a50ca435c 100644 --- a/springwolf-core/src/test/resources/schemas/xml/documented-definitions-xml.json +++ b/springwolf-core/src/test/resources/schemas/xml/documented-definitions-xml.json @@ -29,11 +29,11 @@ "mss" : { "type" : "object", "description" : "Map with example", - "examples" : [ "value1" ], + "examples" : [ "value1" ], "additionalProperties" : { "type" : "string", "description" : "Map with example", - "examples" : [ "value1" ] + "examples" : [ "value1" ] } }, "mss_plain" : { @@ -51,7 +51,7 @@ } }, "description" : "foo model", - "examples" : [ "0
2000-01-01T02:00:00+02:00
truestring2024-04-24strings value
" ], + "examples" : [ "0
2000-01-01T02:00:00+02:00
truestring2024-04-24stringvalue1strings value
" ], "required" : [ "dt", "f", "s" ] }, "io.github.springwolf.core.asyncapi.components.DefaultXmlComponentsServiceTest$SimpleFoo" : { diff --git a/springwolf-core/src/test/resources/schemas/yaml/complex-definitions-yaml.json b/springwolf-core/src/test/resources/schemas/yaml/complex-definitions-yaml.json index 775c1ccf2..c4ef9f2eb 100644 --- a/springwolf-core/src/test/resources/schemas/yaml/complex-definitions-yaml.json +++ b/springwolf-core/src/test/resources/schemas/yaml/complex-definitions-yaml.json @@ -28,7 +28,7 @@ "type" : "string" } }, - "examples" : [ "b: true\nd: 1.1\ndt: \"2015-07-20T15:49:04-07:00\"\nf: 1.1\ni: 0\n\"n\":\n nba: \"YmFzZTY0LWV4YW1wbGU=\"\n nc:\n cyclic: {}\n nli:\n - 0\n nmfm: {}\n ns: \"string\"\n nsm:\n - s: \"string\"\n nu: \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\ns: \"string\"\n" ] + "examples" : [ "b: true\nd: 1.1\ndt: \"2015-07-20T15:49:04-07:00\"\nf: 1.1\ni: 0\n\"n\":\n nba: \"YmFzZTY0LWV4YW1wbGU=\"\n nc:\n cyclic: {}\n nli:\n - 0\n nmfm:\n key:\n s: \"string\"\n ns: \"string\"\n nsm:\n - s: \"string\"\n nu: \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\ns: \"string\"\n" ] }, "io.github.springwolf.core.asyncapi.components.DefaultYamlComponentsServiceTest$ComplexFoo$Nested" : { "type" : "string", @@ -68,7 +68,7 @@ "format" : "uuid" } }, - "examples" : [ "nba: \"YmFzZTY0LWV4YW1wbGU=\"\nnc:\n cyclic: {}\nnli:\n- 0\nnmfm: {}\nns: \"string\"\nnsm:\n- s: \"string\"\nnu: \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n" ] + "examples" : [ "nba: \"YmFzZTY0LWV4YW1wbGU=\"\nnc:\n cyclic: {}\nnli:\n- 0\nnmfm:\n key:\n s: \"string\"\nns: \"string\"\nnsm:\n- s: \"string\"\nnu: \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n" ] }, "io.github.springwolf.core.asyncapi.components.DefaultYamlComponentsServiceTest$ComplexFoo$Nested$Cyclic" : { "type" : "string", @@ -88,4 +88,4 @@ }, "examples" : [ "s: \"string\"\n" ] } -} +} \ No newline at end of file diff --git a/springwolf-core/src/test/resources/schemas/yaml/documented-definitions-yaml.json b/springwolf-core/src/test/resources/schemas/yaml/documented-definitions-yaml.json index f2d06a7ee..a1b551e81 100644 --- a/springwolf-core/src/test/resources/schemas/yaml/documented-definitions-yaml.json +++ b/springwolf-core/src/test/resources/schemas/yaml/documented-definitions-yaml.json @@ -53,7 +53,7 @@ } }, "description" : "foo model", - "examples" : [ "bi: 0\ndt: \"2000-01-01T02:00:00+02:00\"\nf:\n b: true\n s: \"string\"\nld: \"2024-04-24\"\nls_plain:\n- \"string\"\nmss:\n key1: \"value1\"\nmss_plain: {}\ns: \"s value\"\n" ], + "examples" : [ "bi: 0\ndt: \"2000-01-01T02:00:00+02:00\"\nf:\n b: true\n s: \"string\"\nld: \"2024-04-24\"\nls_plain:\n- \"string\"\nmss:\n key1: \"value1\"\nmss_plain:\n key: \"string\"\ns: \"s value\"\n" ], "required" : [ "dt", "f", "s" ] }, "io.github.springwolf.core.asyncapi.components.DefaultYamlComponentsServiceTest$SimpleFoo" : { @@ -68,4 +68,4 @@ }, "examples" : [ "b: true\ns: \"string\"\n" ] } -} +} \ No newline at end of file