From e05a925821e76c10a8163af241ab4cee44746c51 Mon Sep 17 00:00:00 2001 From: David Beck Date: Wed, 9 Oct 2024 17:13:26 +0200 Subject: [PATCH] refactor header-schema creation --- .../components/DefaultComponentsService.java | 4 +- .../common/headers/HeaderClassExtractor.java | 6 +- .../schemas/SwaggerSchemaService.java | 162 +----------------- .../headers/HeaderClassExtractorTest.java | 10 +- .../schemas/SwaggerSchemaServiceTest.java | 17 +- .../src/test/resources/asyncapi.json | 16 +- .../src/test/resources/asyncapi.yaml | 16 +- 7 files changed, 45 insertions(+), 186 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java index 94ef0b16f..9b3b78a76 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/components/DefaultComponentsService.java @@ -30,9 +30,9 @@ public Map getSchemas() { @Override public ComponentSchema resolvePayloadSchema(Type type, String contentType) { - SwaggerSchemaService.Payload payload = schemaService.resolvePayloadSchema(type, contentType); + SwaggerSchemaService.ExtractedSchemas payload = schemaService.resolveSchema(type, contentType); payload.referencedSchemas().forEach(this.schemas::putIfAbsent); - return payload.payloadSchema(); + return payload.rootSchema(); } @Override diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractor.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractor.java index ebf4d735b..1adcae868 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractor.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractor.java @@ -33,8 +33,10 @@ public SchemaObject extractHeader(Method method, PayloadSchemaObject payload) { Header headerAnnotation = argument.getAnnotation(Header.class); String headerName = getHeaderAnnotationName(headerAnnotation); - SchemaObject schema = - schemaService.extractSchema(argument.getType()).getRootSchema(); + SchemaObject schema = schemaService + .extractSchema(argument.getType()) + .rootSchema() + .getSchema(); headers.getProperties().put(headerName, schema); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaService.java index 32036d0de..2f90708ed 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaService.java @@ -15,11 +15,8 @@ import io.swagger.v3.core.util.Json; import io.swagger.v3.core.util.PrimitiveType; import io.swagger.v3.core.util.RefUtils; -import io.swagger.v3.oas.models.media.BooleanSchema; -import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -30,7 +27,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -55,13 +51,7 @@ public SwaggerSchemaService( this.properties = properties; } - public record ExtractedSchemas(String rootSchemaName, Map schemas) { - public SchemaObject getRootSchema() { - return schemas.get(rootSchemaName); - } - } - - public record Payload(ComponentSchema payloadSchema, Map referencedSchemas) {} + public record ExtractedSchemas(ComponentSchema rootSchema, Map referencedSchemas) {} public SchemaObject extractSchema(SchemaObject headers) { String schemaName = headers.getTitle(); @@ -76,35 +66,19 @@ public SchemaObject extractSchema(SchemaObject headers) { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); headerSchema.setProperties(properties); - postProcessSchema(headerSchema, new HashMap<>(Map.of(schemaName, headerSchema)), DEFAULT_CONTENT_TYPE); + postProcessSchema( + new HashMap<>(Map.of(schemaName, headerSchema)), + new HashMap<>(Map.of(schemaName, headerSchema)), + DEFAULT_CONTENT_TYPE); return swaggerSchemaUtil.mapSchema(headerSchema); } public ExtractedSchemas extractSchema(Class type) { - return this.extractSchema(type, ""); + return this.resolveSchema(type, ""); } - public ExtractedSchemas extractSchema(Class type, String contentType) { - String actualContentType = - StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType; - - Map swaggerSchemas = - new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); - - String schemaName = getSchemaName(type, swaggerSchemas); - preProcessSchemas(swaggerSchemas, schemaName, type); - - Map postProcessedSchemas = new HashMap<>(swaggerSchemas); - for (Schema schema : swaggerSchemas.values()) { - postProcessSchema(schema, postProcessedSchemas, actualContentType); - } - - Map schemas = swaggerSchemaUtil.mapSchemasMap(postProcessedSchemas); - return new ExtractedSchemas(schemaName, schemas); - } - - public Payload resolvePayloadSchema(Type type, String contentType) { + public ExtractedSchemas resolveSchema(Type type, String contentType) { String actualContentType = StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType; ResolvedSchema resolvedSchema = runWithFqnSetting( @@ -114,7 +88,7 @@ public Payload resolvePayloadSchema(Type type, String contentType) { // defaulting to stringSchema when resolvedSchema is null SchemaObject payloadSchema = swaggerSchemaUtil.mapSchema( PrimitiveType.fromType(String.class).createProperty()); - return new Payload(ComponentSchema.of(payloadSchema), Map.of()); + return new ExtractedSchemas(ComponentSchema.of(payloadSchema), Map.of()); } else { Map preProcessSchemas = new LinkedHashMap<>(resolvedSchema.referencedSchemas); Schema payloadSchema = resolvedSchema.schema; @@ -122,81 +96,18 @@ public Payload resolvePayloadSchema(Type type, String contentType) { preProcessSchemas(payloadSchema, preProcessSchemas, type); HashMap postProcessSchemas = new HashMap<>(preProcessSchemas); postProcessSchema(preProcessSchemas, postProcessSchemas, actualContentType); - return new Payload( + return new ExtractedSchemas( swaggerSchemaUtil.mapSchemaOrRef(payloadSchema), swaggerSchemaUtil.mapSchemasMap(postProcessSchemas)); } } - private String getSchemaName(Class type, Map schemas) { - if (schemas.isEmpty()) { - // swagger-parser does not create schemas for primitives - if (type.equals(String.class) || type.equals(Character.class) || type.equals(Byte.class)) { - return registerPrimitive(String.class, new StringSchema(), schemas); - } - if (Boolean.class.isAssignableFrom(type)) { - return registerPrimitive(Boolean.class, new BooleanSchema(), schemas); - } - if (Number.class.isAssignableFrom(type)) { - return registerPrimitive(Number.class, new NumberSchema(), schemas); - } - if (Object.class.isAssignableFrom(type)) { - return registerPrimitive(Object.class, new ObjectSchema(), schemas); - } - } - - if (schemas.size() == 1) { - return schemas.keySet().stream().findFirst().get(); - } - - Set resolvedPayloadModelName = - runWithFqnSetting((unused) -> converter.read(type).keySet()); - if (!resolvedPayloadModelName.isEmpty()) { - return resolvedPayloadModelName.stream().findFirst().get(); - } - - return getNameFromClass(type); - } - - private String registerPrimitive(Class type, Schema schema, Map schemas) { - String schemaName = getNameFromClass(type); - schema.setName(schemaName); - - schemas.put(schemaName, schema); - postProcessSchema(schema, schemas, DEFAULT_CONTENT_TYPE); - - return schemaName; - } - - private void preProcessSchemas(Map schemas, String schemaName, Class type) { - processCommonModelConverters(schemas); - processAsyncApiPayloadAnnotation(schemas, schemaName, type); - processSchemaAnnotation(schemas, schemaName, type); - } - private void preProcessSchemas(Schema payloadSchema, Map schemas, Type type) { processCommonModelConverters(payloadSchema, schemas); processAsyncApiPayloadAnnotation(schemas, type); processSchemaAnnotation(payloadSchema, type); } - private void processCommonModelConverters(Map schemas) { - schemas.values().stream() - .filter(schema -> schema.getType() == null) - .filter(schema -> schema.get$ref() != null) - .forEach(schema -> { - String targetSchemaName = schema.getName(); - String sourceSchemaName = StringUtils.substringAfterLast(schema.get$ref(), "/"); - - Schema actualSchema = schemas.get(sourceSchemaName); - - if (actualSchema != null) { - schemas.put(targetSchemaName, actualSchema); - schemas.remove(sourceSchemaName); - } - }); - } - private void processCommonModelConverters(Schema payloadSchema, Map schemas) { schemas.values().stream() .filter(schema -> schema.getType() == null) @@ -225,19 +136,6 @@ private void adaptPayloadSchema(Schema schema, String targetSchemaName, String s } } - private void processSchemaAnnotation(Map schemas, String schemaName, Class type) { - Schema schemaForType = schemas.get(schemaName); - if (schemaForType != null) { - var schemaAnnotation = type.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); - if (schemaAnnotation != null) { - String description = schemaAnnotation.description(); - if (StringUtils.isNotBlank(description)) { - schemaForType.setDescription(description); - } - } - } - } - private void processSchemaAnnotation(Schema payloadSchema, Type type) { JavaType javaType = Json.mapper().constructType(type); Class clazz = javaType.getRawClass(); @@ -249,29 +147,6 @@ private void processSchemaAnnotation(Schema payloadSchema, Type type) { } } - private void processAsyncApiPayloadAnnotation(Map schemas, String schemaName, Class type) { - List withPayloadAnnotatedFields = Arrays.stream(type.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(AsyncApiPayload.class)) - .toList(); - - if (withPayloadAnnotatedFields.size() == 1) { - Schema envelopSchema = schemas.get(schemaName); - if (envelopSchema != null && envelopSchema.getProperties() != null) { - String fieldName = withPayloadAnnotatedFields.get(0).getName(); - Schema actualSchema = (Schema) envelopSchema.getProperties().get(fieldName); - if (actualSchema != null) { - schemas.put(schemaName, actualSchema); - } - } - - } else if (withPayloadAnnotatedFields.size() > 1) { - log.warn( - ("Found more than one field with @AsyncApiPayload annotation in class {}. " - + "Falling back and ignoring annotation."), - type.getName()); - } - } - private void processAsyncApiPayloadAnnotation(Map schemas, Type type) { JavaType javaType = Json.mapper().constructType(type); Class clazz = javaType.getRawClass(); @@ -306,13 +181,6 @@ private R runWithFqnSetting(Function callable) { return result; } - private String getNameFromClass(Class type) { - if (properties.isUseFqn()) { - return type.getName(); - } - return type.getSimpleName(); - } - public String getNameFromType(Type type) { PrimitiveType primitiveType = PrimitiveType.fromType(type); if (primitiveType != null && properties.isUseFqn()) { @@ -330,18 +198,6 @@ public String getSimpleNameFromType(Type type) { return name; } - private void postProcessSchema(Schema schema, Map schemas, String contentType) { - boolean schemasHadEntries = !schemas.isEmpty(); - for (SchemasPostProcessor processor : schemaPostProcessors) { - processor.process(schema, schemas, contentType); - - if (schemasHadEntries && !schemas.containsValue(schema)) { - // If the post-processor removed the schema, we can stop processing - break; - } - } - } - private void postProcessSchema( Map preProcess, Map postProcess, String contentType) { boolean schemasHadEntries = !postProcess.isEmpty(); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractorTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractorTest.java index beec45128..fe597f690 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractorTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/headers/HeaderClassExtractorTest.java @@ -27,14 +27,14 @@ class HeaderClassExtractorTest { "payloadSchemaName", String.class.getSimpleName(), ComponentSchema.of(new SchemaObject())); private final SchemaObject stringSchema = SchemaObject.builder().type(SchemaType.STRING).build(); - private final SchemaObject stringSwaggerSchema = - SchemaObject.builder().type(SchemaType.STRING).build(); + private final ComponentSchema stringSwaggerSchema = + ComponentSchema.of(SchemaObject.builder().type(SchemaType.STRING).build()); @Test void getNoDocumentedHeaders() throws NoSuchMethodException { // given when(schemaService.extractSchema(String.class)) - .thenReturn(new SwaggerSchemaService.ExtractedSchemas("String", Map.of("String", stringSwaggerSchema))); + .thenReturn(new SwaggerSchemaService.ExtractedSchemas(stringSwaggerSchema, Map.of())); // when Method m = TestClass.class.getDeclaredMethod("consumeWithoutHeadersAnnotation", String.class); @@ -48,7 +48,7 @@ void getNoDocumentedHeaders() throws NoSuchMethodException { void getHeaderWithSingleHeaderAnnotation() throws NoSuchMethodException { // given when(schemaService.extractSchema(String.class)) - .thenReturn(new SwaggerSchemaService.ExtractedSchemas("String", Map.of("String", stringSwaggerSchema))); + .thenReturn(new SwaggerSchemaService.ExtractedSchemas(stringSwaggerSchema, Map.of())); // when Method m = TestClass.class.getDeclaredMethod("consumeWithSingleHeaderAnnotation", String.class); @@ -69,7 +69,7 @@ void getHeaderWithSingleHeaderAnnotation() throws NoSuchMethodException { void getHeaderWithMultipleHeaderAnnotation() throws NoSuchMethodException { // given when(schemaService.extractSchema(String.class)) - .thenReturn(new SwaggerSchemaService.ExtractedSchemas("String", Map.of("String", stringSwaggerSchema))); + .thenReturn(new SwaggerSchemaService.ExtractedSchemas(stringSwaggerSchema, Map.of())); // when Method m = TestClass.class.getDeclaredMethod("consumeWithMultipleHeaderAnnotation", String.class, String.class); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaServiceTest.java index 499fd4f43..9c5cc4daa 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaServiceTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaServiceTest.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import io.github.springwolf.core.asyncapi.components.postprocessors.SchemasPostProcessor; import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; @@ -57,11 +58,11 @@ void setUp() { @Test void classWithSchemaAnnotation() { - String modelName = schemaService - .extractSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant") - .rootSchemaName(); + ComponentSchema schema = schemaService + .resolveSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant") + .rootSchema(); - assertThat(modelName).isEqualTo("DifferentName"); + assertThat(schema.getReference().getRef()).isEqualTo("#/components/schemas/DifferentName"); } @Test @@ -77,8 +78,8 @@ void getDefinitionWithoutFqnClassName() throws IOException { Class clazz = OneFieldFooWithoutFqn.class; // swagger seems to cache results. Therefore, a new class must be used. Map schemas = schemaServiceWithFqn - .extractSchema(clazz, "content-type-not-relevant") - .schemas(); + .resolveSchema(clazz, "content-type-not-relevant") + .referencedSchemas(); String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemas); // then @@ -89,7 +90,7 @@ void getDefinitionWithoutFqnClassName() throws IOException { @Test void postProcessorsAreCalled() { - schemaService.extractSchema(ClassWithSchemaAnnotation.class, "some-content-type"); + schemaService.resolveSchema(ClassWithSchemaAnnotation.class, "some-content-type"); verify(schemasPostProcessor).process(any(), any(), eq("some-content-type")); verify(schemasPostProcessor2).process(any(), any(), eq("some-content-type")); @@ -105,7 +106,7 @@ void postProcessorIsSkippedWhenSchemaWasRemoved() { .when(schemasPostProcessor) .process(any(), any(), any()); - schemaService.extractSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant"); + schemaService.resolveSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant"); verifyNoInteractions(schemasPostProcessor2); } diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json index 4d30e319d..5541261b0 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json @@ -443,8 +443,8 @@ "type": "object" } }, - "SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997": { - "title": "SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997", + "SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953": { + "title": "SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953", "type": "object", "properties": { "__TypeId__": { @@ -458,9 +458,9 @@ ] }, "kafka_offset": { - "type": "number", + "type": "integer", "examples": [ - 1.1 + 0 ] }, "kafka_receivedMessageKey": { @@ -473,7 +473,7 @@ "examples": [ { "__TypeId__": "io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto", - "kafka_offset": 1.1, + "kafka_offset": 0, "kafka_receivedMessageKey": "string" } ], @@ -488,13 +488,13 @@ "type": "string" }, "kafka_offset": { - "type": "number" + "type": "integer" }, "kafka_receivedMessageKey": { "type": "string" } }, - "title": "SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997", + "title": "SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953", "type": "object" } }, @@ -1579,7 +1579,7 @@ }, "io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto": { "headers": { - "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997" + "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953" }, "payload": { "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml index 0848ab5b6..f1552a6c2 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml @@ -303,8 +303,8 @@ components: type: string title: SpringKafkaDefaultHeaders-AnotherPayloadDto type: object - SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997: - title: SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997 + SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953: + title: SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953 type: object properties: __TypeId__: @@ -315,16 +315,16 @@ components: examples: - io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto kafka_offset: - type: number + type: integer examples: - - 1.1 + - 0 kafka_receivedMessageKey: type: string examples: - '"string"' examples: - __TypeId__: io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto - kafka_offset: 1.1 + kafka_offset: 0 kafka_receivedMessageKey: string x-json-schema: $schema: https://json-schema.org/draft-04/schema# @@ -335,10 +335,10 @@ components: - io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto type: string kafka_offset: - type: number + type: integer kafka_receivedMessageKey: type: string - title: SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997 + title: SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953 type: object SpringKafkaDefaultHeaders-Message: title: SpringKafkaDefaultHeaders-Message @@ -1145,7 +1145,7 @@ components: bindingVersion: 0.5.0 io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto: headers: - $ref: "#/components/schemas/SpringKafkaDefaultHeaders-ExamplePayloadDto-220435997" + $ref: "#/components/schemas/SpringKafkaDefaultHeaders-ExamplePayloadDto-1316119953" payload: schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0 schema: