From d32f155139304e7a02d027a6479df940f2c7d1c7 Mon Sep 17 00:00:00 2001 From: Lorenz Simon <95355196+lorenz-scalable@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:26:22 +0200 Subject: [PATCH 1/6] feat - Implement Message annotation processing --- kotlin-asyncapi-annotation/pom.xml | 7 + .../kotlinasyncapi/annotation/AsyncApi.kt | 4 + .../annotation/channel/Message.kt | 6 +- .../annotation/channel/MessageExample.kt | 2 +- .../annotation/channel/MessageTrait.kt | 2 +- .../kotlinasyncapi/model/channel/Message.kt | 9 +- kotlin-asyncapi-spring-web/pom.xml | 8 + .../springweb/context/AnnotationProvider.kt | 45 ++++++ .../context/annotation/AnnotationScanner.kt | 27 ++++ .../processor/AnnotationProcessor.kt | 8 + .../annotation/processor/MappingUtils.kt | 147 ++++++++++++++++++ .../annotation/processor/MessageProcessor.kt | 27 ++++ pom.xml | 10 ++ 13 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt diff --git a/kotlin-asyncapi-annotation/pom.xml b/kotlin-asyncapi-annotation/pom.xml index d0d78b5..479de8d 100644 --- a/kotlin-asyncapi-annotation/pom.xml +++ b/kotlin-asyncapi-annotation/pom.xml @@ -13,4 +13,11 @@ Kotlin AsyncAPI Annotation AsyncAPI annotations for meta-documentation + + + + org.openfolder + kotlin-asyncapi-core + + diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt new file mode 100644 index 0000000..17b6c6c --- /dev/null +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt @@ -0,0 +1,4 @@ +package org.openfolder.kotlinasyncapi.annotation + +@Target(AnnotationTarget.ANNOTATION_CLASS) +annotation class AsyncApiAnnotation diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt index 8789832..d18292c 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt @@ -1,5 +1,6 @@ package org.openfolder.kotlinasyncapi.annotation.channel +import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation import org.openfolder.kotlinasyncapi.annotation.CorrelationID import org.openfolder.kotlinasyncapi.annotation.ExternalDocumentation import org.openfolder.kotlinasyncapi.annotation.Schema @@ -12,6 +13,7 @@ import org.openfolder.kotlinasyncapi.annotation.Tag AnnotationTarget.ANNOTATION_CLASS ) @Repeatable +@AsyncApiAnnotation annotation class Message( val default: Boolean = false, val reference: String = "", @@ -22,8 +24,8 @@ annotation class Message( val title: String = "", val summary: String = "", val description: String = "", - val headers: Array = [], - val payload: Schema = Schema(), + val headers: Schema = Schema(default = true), + val payload: Schema = Schema(default = true), val correlationId: CorrelationID = CorrelationID(default = true, location = ""), val tags: Array = [], val externalDocs: ExternalDocumentation = ExternalDocumentation(default = true, url = ""), diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt index 98f0ab3..49d1cd4 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt @@ -4,7 +4,7 @@ import org.openfolder.kotlinasyncapi.annotation.Schema annotation class MessageExample( val default: Boolean = false, - val headers: Schema = Schema(default = true), + val headers: String = "", val payload: String = "", val name: String = "", val summary: String = "" diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageTrait.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageTrait.kt index b15e56a..e6517ca 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageTrait.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageTrait.kt @@ -15,7 +15,7 @@ annotation class MessageTrait( val title: String = "", val summary: String = "", val description: String = "", - val headers: Array = [], + val headers: Schema = Schema(default = true), val correlationId: CorrelationID = CorrelationID(default = true, location = ""), val tags: Array = [], val externalDocs: ExternalDocumentation = ExternalDocumentation(default = true, url = ""), diff --git a/kotlin-asyncapi-core/src/main/kotlin/org/openfolder/kotlinasyncapi/model/channel/Message.kt b/kotlin-asyncapi-core/src/main/kotlin/org/openfolder/kotlinasyncapi/model/channel/Message.kt index 507fcbc..93b7b2b 100644 --- a/kotlin-asyncapi-core/src/main/kotlin/org/openfolder/kotlinasyncapi/model/channel/Message.kt +++ b/kotlin-asyncapi-core/src/main/kotlin/org/openfolder/kotlinasyncapi/model/channel/Message.kt @@ -47,7 +47,7 @@ class Message { var description: String? = null var tags: TagsList? = null var externalDocs: ExternalDocumentation? = null - var bindings: ReferencableMessageBindingsMap? = null + var bindings: Any? = null var examples: MessageExamplesList? = null var traits: MessageTraitsList? = null @@ -96,8 +96,11 @@ class Message { inline fun externalDocs(build: ExternalDocumentation.() -> Unit): ExternalDocumentation = ExternalDocumentation().apply(build).also { externalDocs = it } - inline fun bindings(build: ReferencableMessageBindingsMap.() -> Unit): ReferencableMessageBindingsMap = - ReferencableMessageBindingsMap().apply(build).also { bindings = it } + inline fun bindings(build: MessageBindings.() -> Unit): MessageBindings = + MessageBindings().apply(build).also { bindings = it } + + inline fun bindingsRef(build: Reference.() -> Unit): Reference = + Reference().apply(build).also { bindings = it } inline fun examples(build: MessageExamplesList.() -> Unit): MessageExamplesList = MessageExamplesList().apply(build).also { examples = it } diff --git a/kotlin-asyncapi-spring-web/pom.xml b/kotlin-asyncapi-spring-web/pom.xml index 813e3d5..45b4e1e 100644 --- a/kotlin-asyncapi-spring-web/pom.xml +++ b/kotlin-asyncapi-spring-web/pom.xml @@ -23,10 +23,18 @@ org.openfolder kotlin-asyncapi-script + + org.openfolder + kotlin-asyncapi-annotation + com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.module + jackson-module-jsonSchema + org.springframework spring-web diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt new file mode 100644 index 0000000..a31c533 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt @@ -0,0 +1,45 @@ +package org.openfolder.kotlinasyncapi.springweb.context + +import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.model.component.Components +import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi +import org.openfolder.kotlinasyncapi.springweb.context.annotation.AnnotationScanner +import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.AnnotationProcessor +import org.springframework.context.ApplicationContext +import org.springframework.stereotype.Component + +internal interface AnnotationProvider { + + val components: Components? +} + +@Component +internal class DefaultAnnotationProvider( + context: ApplicationContext, + scanner: AnnotationScanner, + messageProcessor: AnnotationProcessor +): AnnotationProvider { + + override val components: Components? by lazy { + val scanPackage = context.getBeansWithAnnotation(EnableAsyncApi::class.java).values + .firstOrNull() + ?.let { it::class.java.packageName } + ?.takeIf { it.isNotEmpty() } + + val annotatedClasses = scanner.scan(scanPackage = scanPackage!!, annotation = AsyncApiAnnotation::class) + val annotationComponents = mutableListOf() + + for (clazz in annotatedClasses) { + for (annotation in clazz.annotations) { + if (annotation.annotationClass == Message::class) { + annotationComponents.add(messageProcessor.process(annotation as Message, clazz)) + } + } + } + + Components().merge(*annotationComponents.toTypedArray()) + } + + private fun Components.merge(vararg components: Components): Components = this // TODO +} diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt new file mode 100644 index 0000000..3fc5cb5 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt @@ -0,0 +1,27 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation + +import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation +import org.springframework.context.ApplicationContext +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider +import org.springframework.core.type.filter.AnnotationTypeFilter +import org.springframework.stereotype.Component +import kotlin.reflect.KClass + +internal interface AnnotationScanner { + fun scan(scanPackage: String, annotation: KClass): List> +} + +@Component +internal class DefaultAnnotationScanner( + private val context: ApplicationContext +): AnnotationScanner { + override fun scan(scanPackage: String, annotation: KClass): List> { + val classPathScanner = ClassPathScanningCandidateComponentProvider(false).also { + it.addIncludeFilter(AnnotationTypeFilter(AsyncApiAnnotation::class.java)) + } + + return classPathScanner.findCandidateComponents(scanPackage).map { + Class.forName(it.beanClassName).kotlin + }.toList() + } +} diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt new file mode 100644 index 0000000..5e1083a --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt @@ -0,0 +1,8 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import org.openfolder.kotlinasyncapi.model.component.Components +import kotlin.reflect.KClass + +internal interface AnnotationProcessor { + fun process(annotation: T, clazz: KClass<*>?): Components +} diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt new file mode 100644 index 0000000..d37be6d --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt @@ -0,0 +1,147 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator +import org.openfolder.kotlinasyncapi.annotation.CorrelationID +import org.openfolder.kotlinasyncapi.annotation.ExternalDocumentation +import org.openfolder.kotlinasyncapi.annotation.Tag +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.annotation.channel.MessageBinding +import org.openfolder.kotlinasyncapi.annotation.channel.MessageBindings +import org.openfolder.kotlinasyncapi.annotation.channel.MessageExample +import org.openfolder.kotlinasyncapi.annotation.channel.MessageTrait +import org.openfolder.kotlinasyncapi.model.TagsList +import org.openfolder.kotlinasyncapi.model.channel.MessageExamplesList +import org.openfolder.kotlinasyncapi.model.channel.MessageTraitsList + +val objectMapper = ObjectMapper() +val jsonSchemaGenerator = JsonSchemaGenerator(objectMapper) + +fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Message = + org.openfolder.kotlinasyncapi.model.channel.Message().apply { + messageId = this@toMessage.messageId.takeIf { it.isNotEmpty() } + schemaFormat = this@toMessage.schemaFormat.takeIf { it.isNotEmpty() } + contentType = this@toMessage.contentType.takeIf { it.isNotEmpty() } + name = this@toMessage.name.takeIf { it.isNotEmpty() } + title = this@toMessage.title.takeIf { it.isNotEmpty() } + summary = this@toMessage.summary.takeIf { it.isNotEmpty() } + description = this@toMessage.description.takeIf { it.isNotEmpty() } + correlationId = this@toMessage.correlationId.takeUnless { it.default }?.toCorrelationID() + tags = this@toMessage.tags.map { it.toTag() }.toList() as TagsList + externalDocs = this@toMessage.externalDocs.takeUnless { it.default }?.toExternalDocumentation() + examples = this@toMessage.examples.map { it.toMessageExample() }.toList() as MessageExamplesList + bindings = this@toMessage.bindings.takeUnless { it.default }?.toMessageBindings() + traits = this@toMessage.traits.map { it.toMessageTrait() }.toList() as MessageTraitsList + headers = this@toMessage.headers.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + payload = this@toMessage.payload.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + } + +fun CorrelationID.toCorrelationID(): org.openfolder.kotlinasyncapi.model.CorrelationID = + org.openfolder.kotlinasyncapi.model.CorrelationID().apply { + location = this@toCorrelationID.location + description = this@toCorrelationID.description.takeIf { it.isNotEmpty() } + } + +fun ExternalDocumentation.toExternalDocumentation(): org.openfolder.kotlinasyncapi.model.ExternalDocumentation = + org.openfolder.kotlinasyncapi.model.ExternalDocumentation().apply { + url = this@toExternalDocumentation.url + description = this@toExternalDocumentation.description.takeIf { it.isNotEmpty() } + } + +fun Tag.toTag(): org.openfolder.kotlinasyncapi.model.Tag = + org.openfolder.kotlinasyncapi.model.Tag().apply { + name = this@toTag.name + description = this@toTag.description.takeIf { it.isNotEmpty() } + externalDocs = this@toTag.externalDocs.takeUnless { it.default }?.toExternalDocumentation() + } + +fun MessageExample.toMessageExample(): org.openfolder.kotlinasyncapi.model.channel.MessageExample = + org.openfolder.kotlinasyncapi.model.channel.MessageExample().apply { + name = this@toMessageExample.name.takeIf { it.isNotEmpty() } + summary = this@toMessageExample.summary.takeIf { it.isNotEmpty() } + payload = this@toMessageExample.payload.takeIf { it.isNotEmpty() }?.let { + objectMapper.readValue(it, object : TypeReference() {}) + } + headers = this@toMessageExample.headers.takeIf { it.isNotEmpty() }?.let { + objectMapper.readValue(it, object : TypeReference>() {}) + } + } + +fun MessageBindings.toMessageBindings(): org.openfolder.kotlinasyncapi.model.channel.MessageBindings = + org.openfolder.kotlinasyncapi.model.channel.MessageBindings().apply { + http = this@toMessageBindings.http.takeUnless { it.default }?.toHTTP() + kafka = this@toMessageBindings.kafka.takeUnless { it.default }?.toKafka() + anypointmq = this@toMessageBindings.anypointmq.takeUnless { it.default }?.toAnypointMQ() + amqp = this@toMessageBindings.amqp.takeUnless { it.default }?.toAMQP() + mqtt = this@toMessageBindings.mqtt.takeUnless { it.default }?.toMQTT() + ibmmq = this@toMessageBindings.ibmmq.takeUnless { it.default }?.toIBMMQ() + } + +fun MessageTrait.toMessageTrait(): org.openfolder.kotlinasyncapi.model.channel.MessageTrait = + org.openfolder.kotlinasyncapi.model.channel.MessageTrait().apply { + messageId = this@toMessageTrait.messageId.takeIf { it.isNotEmpty() } + schemaFormat = this@toMessageTrait.schemaFormat.takeIf { it.isNotEmpty() } + contentType = this@toMessageTrait.contentType.takeIf { it.isNotEmpty() } + name = this@toMessageTrait.name.takeIf { it.isNotEmpty() } + title = this@toMessageTrait.title.takeIf { it.isNotEmpty() } + summary = this@toMessageTrait.summary.takeIf { it.isNotEmpty() } + description = this@toMessageTrait.description.takeIf { it.isNotEmpty() } + correlationId = this@toMessageTrait.correlationId.takeUnless { it.default }?.toCorrelationID() + tags = this@toMessageTrait.tags.map { it.toTag() }.toList() as TagsList + externalDocs = this@toMessageTrait.externalDocs.takeUnless { it.default }?.toExternalDocumentation() + examples = this@toMessageTrait.examples.map { it.toMessageExample() }.toList() as MessageExamplesList + bindings = this@toMessageTrait.bindings.takeUnless { it.default }?.toMessageBindings() + headers = this@toMessageTrait.headers.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + } + +fun MessageBinding.HTTP.toHTTP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP().apply { + bindingVersion = this@toHTTP.bindingVersion.takeIf { it.isNotEmpty() } + headers = this@toHTTP.headers.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + } + +fun MessageBinding.Kafka.toKafka(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka().apply { + bindingVersion = this@toKafka.bindingVersion.takeIf { it.isNotEmpty() } + key = this@toKafka.key.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + } + +fun MessageBinding.AnypointMQ.toAnypointMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ().apply { + bindingVersion = this@toAnypointMQ.bindingVersion.takeIf { it.isNotEmpty() } + headers = this@toAnypointMQ.headers.takeUnless { it.default }?.implementation?.let { + jsonSchemaGenerator.generateSchema(it.java) + } + } + +fun MessageBinding.AMQP.toAMQP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AMQP = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AMQP().apply { + bindingVersion = this@toAMQP.bindingVersion.takeIf { it.isNotEmpty() } + messageType = this@toAMQP.messageType.takeIf { it.isNotEmpty() } + contentEncoding = this@toAMQP.contentEncoding.takeIf { it.isNotEmpty() } + } + +fun MessageBinding.MQTT.toMQTT(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.MQTT = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.MQTT().apply { + bindingVersion = this@toMQTT.bindingVersion.takeIf { it.isNotEmpty() } + } + +fun MessageBinding.IBMMQ.toIBMMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.IBMMQ = + org.openfolder.kotlinasyncapi.model.channel.MessageBinding.IBMMQ().apply { + bindingVersion = this@toIBMMQ.bindingVersion.takeIf { it.isNotEmpty() } + type = this@toIBMMQ.type.takeIf { it.isNotEmpty() } + description = this@toIBMMQ.description.takeIf { it.isNotEmpty() } + expiry = this@toIBMMQ.expiry + headers = this@toIBMMQ.headers.takeIf { it.isNotEmpty() } + } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt new file mode 100644 index 0000000..10fbd01 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt @@ -0,0 +1,27 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.model.component.Components +import org.springframework.stereotype.Component +import kotlin.reflect.KClass + +@Component +class MessageProcessor: AnnotationProcessor { + override fun process(annotation: Message, clazz: KClass<*>?): Components { + val objectMapper = ObjectMapper() + val schemaGenerator = JsonSchemaGenerator(objectMapper) + val jsonSchema = clazz?.let { + schemaGenerator.generateSchema(it.java) + } + + return Components().apply { + messages { + annotation.toMessage() + .apply { payload = payload ?: jsonSchema } + .let { put(clazz.toString(), it) } + } + } + } +} diff --git a/pom.xml b/pom.xml index 17c3ce0..dc877ea 100644 --- a/pom.xml +++ b/pom.xml @@ -235,11 +235,21 @@ kotlin-asyncapi-script ${project.version} + + org.openfolder + kotlin-asyncapi-annotation + ${project.version} + com.fasterxml.jackson.core jackson-databind 2.14.1 + + com.fasterxml.jackson.module + jackson-module-jsonSchema + 2.14.1 + org.jetbrains.kotlin kotlin-scripting-jvm-host From 57d7c9203ffe379bb549c96babcab1c596d454d5 Mon Sep 17 00:00:00 2001 From: Lorenz Simon <95355196+lorenz-scalable@users.noreply.github.com> Date: Wed, 15 Mar 2023 15:57:23 +0200 Subject: [PATCH 2/6] feat - Merge annotation components --- .../springweb/context/AnnotationProvider.kt | 65 ++++++++++++++++++- .../annotation/processor/MappingUtils.kt | 30 ++++----- .../annotation/processor/MessageProcessor.kt | 2 +- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt index a31c533..0d9fde5 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt @@ -2,7 +2,21 @@ package org.openfolder.kotlinasyncapi.springweb.context import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.model.ReferencableCorrelationIDsMap +import org.openfolder.kotlinasyncapi.model.ReferencableSchemasMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableChannelBindingsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableChannelsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableMessageBindingsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableMessageTraitsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableMessagesMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableOperationBindingsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableOperationTraitsMap +import org.openfolder.kotlinasyncapi.model.channel.ReferencableParametersMap import org.openfolder.kotlinasyncapi.model.component.Components +import org.openfolder.kotlinasyncapi.model.component.ReferencableSecuritySchemasMap +import org.openfolder.kotlinasyncapi.model.server.ReferencableServerBindingsMap +import org.openfolder.kotlinasyncapi.model.server.ReferencableServerVariablesMap +import org.openfolder.kotlinasyncapi.model.server.ReferencableServersMap import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi import org.openfolder.kotlinasyncapi.springweb.context.annotation.AnnotationScanner import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.AnnotationProcessor @@ -38,8 +52,55 @@ internal class DefaultAnnotationProvider( } } - Components().merge(*annotationComponents.toTypedArray()) + Components().merge(annotationComponents) } - private fun Components.merge(vararg components: Components): Components = this // TODO + private fun Components.merge(components: List): Components { + for (component in components) { + component.schemas?.also { + schemas = (schemas ?: ReferencableSchemasMap()).apply { putAll(it) } + } + component.servers?.also { + servers = (servers ?: ReferencableServersMap()).apply { putAll(it) } + } + component.serverVariables?.also { + serverVariables = (serverVariables ?: ReferencableServerVariablesMap()).apply { putAll(it) } + } + component.channels?.also { + channels = (channels ?: ReferencableChannelsMap()).apply { putAll(it) } + } + component.messages?.also { + messages = (messages ?: ReferencableMessagesMap()).apply { putAll(it) } + } + component.securitySchemes?.also { + securitySchemes = (securitySchemes ?: ReferencableSecuritySchemasMap()).apply { putAll(it) } + } + component.parameters?.also { + parameters = (parameters ?: ReferencableParametersMap()).apply { putAll(it) } + } + component.correlationIds?.also { + correlationIds = (correlationIds ?: ReferencableCorrelationIDsMap()).apply { putAll(it) } + } + component.operationTraits?.also { + operationTraits = (operationTraits ?: ReferencableOperationTraitsMap()).apply { putAll(it) } + } + component.messageTraits?.also { + messageTraits = (messageTraits ?: ReferencableMessageTraitsMap()).apply { putAll(it) } + } + component.serverBindings?.also { + serverBindings = (serverBindings ?: ReferencableServerBindingsMap()).apply { putAll(it) } + } + component.channelBindings?.also { + channelBindings = (channelBindings ?: ReferencableChannelBindingsMap()).apply { putAll(it) } + } + component.operationBindings?.also { + operationBindings = (operationBindings ?: ReferencableOperationBindingsMap()).apply { putAll(it) } + } + component.messageBindings?.also { + messageBindings = (messageBindings ?: ReferencableMessageBindingsMap()).apply { putAll(it) } + } + } + + return this + } } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt index d37be6d..9a32d1d 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt @@ -15,10 +15,10 @@ import org.openfolder.kotlinasyncapi.model.TagsList import org.openfolder.kotlinasyncapi.model.channel.MessageExamplesList import org.openfolder.kotlinasyncapi.model.channel.MessageTraitsList -val objectMapper = ObjectMapper() -val jsonSchemaGenerator = JsonSchemaGenerator(objectMapper) +internal val objectMapper = ObjectMapper() +internal val jsonSchemaGenerator = JsonSchemaGenerator(objectMapper) -fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Message = +internal fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Message = org.openfolder.kotlinasyncapi.model.channel.Message().apply { messageId = this@toMessage.messageId.takeIf { it.isNotEmpty() } schemaFormat = this@toMessage.schemaFormat.takeIf { it.isNotEmpty() } @@ -41,26 +41,26 @@ fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Message = } } -fun CorrelationID.toCorrelationID(): org.openfolder.kotlinasyncapi.model.CorrelationID = +internal fun CorrelationID.toCorrelationID(): org.openfolder.kotlinasyncapi.model.CorrelationID = org.openfolder.kotlinasyncapi.model.CorrelationID().apply { location = this@toCorrelationID.location description = this@toCorrelationID.description.takeIf { it.isNotEmpty() } } -fun ExternalDocumentation.toExternalDocumentation(): org.openfolder.kotlinasyncapi.model.ExternalDocumentation = +internal fun ExternalDocumentation.toExternalDocumentation(): org.openfolder.kotlinasyncapi.model.ExternalDocumentation = org.openfolder.kotlinasyncapi.model.ExternalDocumentation().apply { url = this@toExternalDocumentation.url description = this@toExternalDocumentation.description.takeIf { it.isNotEmpty() } } -fun Tag.toTag(): org.openfolder.kotlinasyncapi.model.Tag = +internal fun Tag.toTag(): org.openfolder.kotlinasyncapi.model.Tag = org.openfolder.kotlinasyncapi.model.Tag().apply { name = this@toTag.name description = this@toTag.description.takeIf { it.isNotEmpty() } externalDocs = this@toTag.externalDocs.takeUnless { it.default }?.toExternalDocumentation() } -fun MessageExample.toMessageExample(): org.openfolder.kotlinasyncapi.model.channel.MessageExample = +internal fun MessageExample.toMessageExample(): org.openfolder.kotlinasyncapi.model.channel.MessageExample = org.openfolder.kotlinasyncapi.model.channel.MessageExample().apply { name = this@toMessageExample.name.takeIf { it.isNotEmpty() } summary = this@toMessageExample.summary.takeIf { it.isNotEmpty() } @@ -72,7 +72,7 @@ fun MessageExample.toMessageExample(): org.openfolder.kotlinasyncapi.model.chann } } -fun MessageBindings.toMessageBindings(): org.openfolder.kotlinasyncapi.model.channel.MessageBindings = +internal fun MessageBindings.toMessageBindings(): org.openfolder.kotlinasyncapi.model.channel.MessageBindings = org.openfolder.kotlinasyncapi.model.channel.MessageBindings().apply { http = this@toMessageBindings.http.takeUnless { it.default }?.toHTTP() kafka = this@toMessageBindings.kafka.takeUnless { it.default }?.toKafka() @@ -82,7 +82,7 @@ fun MessageBindings.toMessageBindings(): org.openfolder.kotlinasyncapi.model.cha ibmmq = this@toMessageBindings.ibmmq.takeUnless { it.default }?.toIBMMQ() } -fun MessageTrait.toMessageTrait(): org.openfolder.kotlinasyncapi.model.channel.MessageTrait = +internal fun MessageTrait.toMessageTrait(): org.openfolder.kotlinasyncapi.model.channel.MessageTrait = org.openfolder.kotlinasyncapi.model.channel.MessageTrait().apply { messageId = this@toMessageTrait.messageId.takeIf { it.isNotEmpty() } schemaFormat = this@toMessageTrait.schemaFormat.takeIf { it.isNotEmpty() } @@ -101,7 +101,7 @@ fun MessageTrait.toMessageTrait(): org.openfolder.kotlinasyncapi.model.channel.M } } -fun MessageBinding.HTTP.toHTTP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP = +internal fun MessageBinding.HTTP.toHTTP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP().apply { bindingVersion = this@toHTTP.bindingVersion.takeIf { it.isNotEmpty() } headers = this@toHTTP.headers.takeUnless { it.default }?.implementation?.let { @@ -109,7 +109,7 @@ fun MessageBinding.HTTP.toHTTP(): org.openfolder.kotlinasyncapi.model.channel.Me } } -fun MessageBinding.Kafka.toKafka(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka = +internal fun MessageBinding.Kafka.toKafka(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka().apply { bindingVersion = this@toKafka.bindingVersion.takeIf { it.isNotEmpty() } key = this@toKafka.key.takeUnless { it.default }?.implementation?.let { @@ -117,7 +117,7 @@ fun MessageBinding.Kafka.toKafka(): org.openfolder.kotlinasyncapi.model.channel. } } -fun MessageBinding.AnypointMQ.toAnypointMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ = +internal fun MessageBinding.AnypointMQ.toAnypointMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ().apply { bindingVersion = this@toAnypointMQ.bindingVersion.takeIf { it.isNotEmpty() } headers = this@toAnypointMQ.headers.takeUnless { it.default }?.implementation?.let { @@ -125,19 +125,19 @@ fun MessageBinding.AnypointMQ.toAnypointMQ(): org.openfolder.kotlinasyncapi.mode } } -fun MessageBinding.AMQP.toAMQP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AMQP = +internal fun MessageBinding.AMQP.toAMQP(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AMQP = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AMQP().apply { bindingVersion = this@toAMQP.bindingVersion.takeIf { it.isNotEmpty() } messageType = this@toAMQP.messageType.takeIf { it.isNotEmpty() } contentEncoding = this@toAMQP.contentEncoding.takeIf { it.isNotEmpty() } } -fun MessageBinding.MQTT.toMQTT(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.MQTT = +internal fun MessageBinding.MQTT.toMQTT(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.MQTT = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.MQTT().apply { bindingVersion = this@toMQTT.bindingVersion.takeIf { it.isNotEmpty() } } -fun MessageBinding.IBMMQ.toIBMMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.IBMMQ = +internal fun MessageBinding.IBMMQ.toIBMMQ(): org.openfolder.kotlinasyncapi.model.channel.MessageBinding.IBMMQ = org.openfolder.kotlinasyncapi.model.channel.MessageBinding.IBMMQ().apply { bindingVersion = this@toIBMMQ.bindingVersion.takeIf { it.isNotEmpty() } type = this@toIBMMQ.type.takeIf { it.isNotEmpty() } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt index 10fbd01..a877022 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt @@ -8,7 +8,7 @@ import org.springframework.stereotype.Component import kotlin.reflect.KClass @Component -class MessageProcessor: AnnotationProcessor { +internal class MessageProcessor: AnnotationProcessor { override fun process(annotation: Message, clazz: KClass<*>?): Components { val objectMapper = ObjectMapper() val schemaGenerator = JsonSchemaGenerator(objectMapper) From 12698d1c8d36595f18b43390a0e53ddd76e8366a Mon Sep 17 00:00:00 2001 From: Lorenz Simon <95355196+lorenz-scalable@users.noreply.github.com> Date: Fri, 17 Mar 2023 11:46:00 +0200 Subject: [PATCH 3/6] feat - Add schema annotation processor --- .../kotlinasyncapi/annotation/Schema.kt | 35 ++---- .../annotation/channel/Message.kt | 2 - .../model/AsyncApiIntegrationTest.kt | 12 +- .../test/resources/async_api_integration.json | 8 +- kotlin-asyncapi-spring-web/pom.xml | 5 +- .../springweb/context/AnnotationProvider.kt | 118 +++++++++--------- .../processor/AnnotationProcessor.kt | 2 +- .../annotation/processor/MappingUtils.kt | 44 ++++--- .../annotation/processor/MessageProcessor.kt | 27 ++-- .../annotation/processor/SchemaProcessor.kt | 21 ++++ .../context/DefaultAnnotationProviderTest.kt | 42 +++++++ .../processor/MessageProcessorTest.kt | 43 +++++++ .../processor/SchemaProcessorTest.kt | 35 ++++++ .../AsyncApiControllerIntegrationTest.kt | 12 +- .../annotation/message_component.json | 43 +++++++ .../annotation/schema_component.json | 41 ++++++ .../test/resources/async_api_integration.json | 8 +- pom.xml | 7 +- 18 files changed, 357 insertions(+), 148 deletions(-) create mode 100644 kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/resources/annotation/message_component.json create mode 100644 kotlin-asyncapi-spring-web/src/test/resources/annotation/schema_component.json diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt index ab7fc87..06bad2b 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt @@ -2,34 +2,13 @@ package org.openfolder.kotlinasyncapi.annotation import kotlin.reflect.KClass +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.ANNOTATION_CLASS +) +@Repeatable +@AsyncApiAnnotation annotation class Schema( val default: Boolean = false, - val reference: String = "", - val title: String = "", - val description: String = "", - val implementation: KClass<*> = Void::class, - val readOnly: Boolean = false, - val writeOnly: Boolean = false, - val examples: Array = [], - val type: String = "", - val multipleOf: Int = 0, - val maximum: Int = 0, - val exclusiveMaximum: Int = 0, - val minimum: Int = 0, - val exclusiveMinimum: Int = 0, - val maxLength: Int = 0, - val minLength: Int = 0, - val pattern: String = "", - val maxProperties: Int = 0, - val minProperties: Int = 0, - val required: Array = [], - val additionalProperties: String = "", - val allOf: Array> = [], - val anyOf: Array> = [], - val oneOf: Array> = [], - val not: KClass<*> = Void::class, - val format: String = "", - val discriminator: String = "", - val externalDocs: ExternalDocumentation = ExternalDocumentation(default = true, url = ""), - val deprecated: Boolean = false + val implementation: KClass<*> = Void::class ) diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt index d18292c..f02811d 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/Message.kt @@ -8,8 +8,6 @@ import org.openfolder.kotlinasyncapi.annotation.Tag @Target( AnnotationTarget.CLASS, - AnnotationTarget.FUNCTION, - AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.ANNOTATION_CLASS ) @Repeatable diff --git a/kotlin-asyncapi-core/src/test/kotlin/org/openfolder/kotlinasyncapi/model/AsyncApiIntegrationTest.kt b/kotlin-asyncapi-core/src/test/kotlin/org/openfolder/kotlinasyncapi/model/AsyncApiIntegrationTest.kt index 8b28ddb..9b23679 100644 --- a/kotlin-asyncapi-core/src/test/kotlin/org/openfolder/kotlinasyncapi/model/AsyncApiIntegrationTest.kt +++ b/kotlin-asyncapi-core/src/test/kotlin/org/openfolder/kotlinasyncapi/model/AsyncApiIntegrationTest.kt @@ -79,10 +79,8 @@ internal class AsyncApiIntegrationTest { } } } - bindings { - reference("http") { - ref("#/components/messageBindings/streamingHeaders") - } + bindingsRef { + ref("#/components/messageBindings/streamingHeaders") } } message("heartbeat") { @@ -92,10 +90,8 @@ internal class AsyncApiIntegrationTest { type("string") enum("\r\n") } - bindings { - reference("http") { - ref("#/components/messageBindings/streamingHeaders") - } + bindingsRef { + ref("#/components/messageBindings/streamingHeaders") } } } diff --git a/kotlin-asyncapi-core/src/test/resources/async_api_integration.json b/kotlin-asyncapi-core/src/test/resources/async_api_integration.json index 3ca015d..9277f9e 100644 --- a/kotlin-asyncapi-core/src/test/resources/async_api_integration.json +++ b/kotlin-asyncapi-core/src/test/resources/async_api_integration.json @@ -61,9 +61,7 @@ "schemaFormat" : "application/schema+yaml;version=draft-07", "summary" : "A message represents an individual chat message sent to a room.", "bindings" : { - "http" : { - "$ref" : "#/components/messageBindings/streamingHeaders" - } + "$ref" : "#/components/messageBindings/streamingHeaders" } }, "heartbeat" : { @@ -74,9 +72,7 @@ "schemaFormat" : "application/schema+yaml;version=draft-07", "summary" : "Its purpose is to keep the connection alive.", "bindings" : { - "http" : { - "$ref" : "#/components/messageBindings/streamingHeaders" - } + "$ref" : "#/components/messageBindings/streamingHeaders" } } }, diff --git a/kotlin-asyncapi-spring-web/pom.xml b/kotlin-asyncapi-spring-web/pom.xml index 45b4e1e..56823ce 100644 --- a/kotlin-asyncapi-spring-web/pom.xml +++ b/kotlin-asyncapi-spring-web/pom.xml @@ -32,8 +32,9 @@ jackson-databind - com.fasterxml.jackson.module - jackson-module-jsonSchema + io.swagger.core.v3 + swagger-core + 2.2.8 org.springframework diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt index 0d9fde5..e80569e 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt @@ -1,6 +1,7 @@ package org.openfolder.kotlinasyncapi.springweb.context import org.openfolder.kotlinasyncapi.annotation.AsyncApiAnnotation +import org.openfolder.kotlinasyncapi.annotation.Schema import org.openfolder.kotlinasyncapi.annotation.channel.Message import org.openfolder.kotlinasyncapi.model.ReferencableCorrelationIDsMap import org.openfolder.kotlinasyncapi.model.ReferencableSchemasMap @@ -22,6 +23,7 @@ import org.openfolder.kotlinasyncapi.springweb.context.annotation.AnnotationScan import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.AnnotationProcessor import org.springframework.context.ApplicationContext import org.springframework.stereotype.Component +import kotlin.reflect.full.findAnnotation internal interface AnnotationProvider { @@ -32,8 +34,9 @@ internal interface AnnotationProvider { internal class DefaultAnnotationProvider( context: ApplicationContext, scanner: AnnotationScanner, - messageProcessor: AnnotationProcessor -): AnnotationProvider { + messageProcessor: AnnotationProcessor, + schemaProcessor: AnnotationProcessor +) : AnnotationProvider { override val components: Components? by lazy { val scanPackage = context.getBeansWithAnnotation(EnableAsyncApi::class.java).values @@ -42,65 +45,68 @@ internal class DefaultAnnotationProvider( ?.takeIf { it.isNotEmpty() } val annotatedClasses = scanner.scan(scanPackage = scanPackage!!, annotation = AsyncApiAnnotation::class) - val annotationComponents = mutableListOf() - for (clazz in annotatedClasses) { - for (annotation in clazz.annotations) { - if (annotation.annotationClass == Message::class) { - annotationComponents.add(messageProcessor.process(annotation as Message, clazz)) + Components().apply { + annotatedClasses + .flatMap { clazz -> + listOfNotNull( + clazz.findAnnotation()?.let { clazz to it }, + clazz.findAnnotation()?.let { clazz to it } + ) } - } + .mapNotNull { (clazz, annotation) -> + when (annotation) { + is Message -> messageProcessor.process(annotation, clazz) + is Schema -> schemaProcessor.process(annotation, clazz) + else -> null + } + } + .forEach { this + it } } - - Components().merge(annotationComponents) } - private fun Components.merge(components: List): Components { - for (component in components) { - component.schemas?.also { - schemas = (schemas ?: ReferencableSchemasMap()).apply { putAll(it) } - } - component.servers?.also { - servers = (servers ?: ReferencableServersMap()).apply { putAll(it) } - } - component.serverVariables?.also { - serverVariables = (serverVariables ?: ReferencableServerVariablesMap()).apply { putAll(it) } - } - component.channels?.also { - channels = (channels ?: ReferencableChannelsMap()).apply { putAll(it) } - } - component.messages?.also { - messages = (messages ?: ReferencableMessagesMap()).apply { putAll(it) } - } - component.securitySchemes?.also { - securitySchemes = (securitySchemes ?: ReferencableSecuritySchemasMap()).apply { putAll(it) } - } - component.parameters?.also { - parameters = (parameters ?: ReferencableParametersMap()).apply { putAll(it) } - } - component.correlationIds?.also { - correlationIds = (correlationIds ?: ReferencableCorrelationIDsMap()).apply { putAll(it) } - } - component.operationTraits?.also { - operationTraits = (operationTraits ?: ReferencableOperationTraitsMap()).apply { putAll(it) } - } - component.messageTraits?.also { - messageTraits = (messageTraits ?: ReferencableMessageTraitsMap()).apply { putAll(it) } - } - component.serverBindings?.also { - serverBindings = (serverBindings ?: ReferencableServerBindingsMap()).apply { putAll(it) } - } - component.channelBindings?.also { - channelBindings = (channelBindings ?: ReferencableChannelBindingsMap()).apply { putAll(it) } - } - component.operationBindings?.also { - operationBindings = (operationBindings ?: ReferencableOperationBindingsMap()).apply { putAll(it) } - } - component.messageBindings?.also { - messageBindings = (messageBindings ?: ReferencableMessageBindingsMap()).apply { putAll(it) } - } + private operator fun Components.plus(components: Components) { + components.schemas?.also { + schemas = (schemas ?: ReferencableSchemasMap()).apply { putAll(it) } + } + components.servers?.also { + servers = (servers ?: ReferencableServersMap()).apply { putAll(it) } + } + components.serverVariables?.also { + serverVariables = (serverVariables ?: ReferencableServerVariablesMap()).apply { putAll(it) } + } + components.channels?.also { + channels = (channels ?: ReferencableChannelsMap()).apply { putAll(it) } + } + components.messages?.also { + messages = (messages ?: ReferencableMessagesMap()).apply { putAll(it) } + } + components.securitySchemes?.also { + securitySchemes = (securitySchemes ?: ReferencableSecuritySchemasMap()).apply { putAll(it) } + } + components.parameters?.also { + parameters = (parameters ?: ReferencableParametersMap()).apply { putAll(it) } + } + components.correlationIds?.also { + correlationIds = (correlationIds ?: ReferencableCorrelationIDsMap()).apply { putAll(it) } + } + components.operationTraits?.also { + operationTraits = (operationTraits ?: ReferencableOperationTraitsMap()).apply { putAll(it) } + } + components.messageTraits?.also { + messageTraits = (messageTraits ?: ReferencableMessageTraitsMap()).apply { putAll(it) } + } + components.serverBindings?.also { + serverBindings = (serverBindings ?: ReferencableServerBindingsMap()).apply { putAll(it) } + } + components.channelBindings?.also { + channelBindings = (channelBindings ?: ReferencableChannelBindingsMap()).apply { putAll(it) } + } + components.operationBindings?.also { + operationBindings = (operationBindings ?: ReferencableOperationBindingsMap()).apply { putAll(it) } + } + components.messageBindings?.also { + messageBindings = (messageBindings ?: ReferencableMessageBindingsMap()).apply { putAll(it) } } - - return this } } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt index 5e1083a..03f03fa 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt @@ -4,5 +4,5 @@ import org.openfolder.kotlinasyncapi.model.component.Components import kotlin.reflect.KClass internal interface AnnotationProcessor { - fun process(annotation: T, clazz: KClass<*>?): Components + fun process(annotation: T, clazz: KClass<*>): Components } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt index 9a32d1d..cc13a70 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MappingUtils.kt @@ -2,7 +2,6 @@ package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator import org.openfolder.kotlinasyncapi.annotation.CorrelationID import org.openfolder.kotlinasyncapi.annotation.ExternalDocumentation import org.openfolder.kotlinasyncapi.annotation.Tag @@ -11,13 +10,11 @@ import org.openfolder.kotlinasyncapi.annotation.channel.MessageBinding import org.openfolder.kotlinasyncapi.annotation.channel.MessageBindings import org.openfolder.kotlinasyncapi.annotation.channel.MessageExample import org.openfolder.kotlinasyncapi.annotation.channel.MessageTrait +import org.openfolder.kotlinasyncapi.model.Reference import org.openfolder.kotlinasyncapi.model.TagsList import org.openfolder.kotlinasyncapi.model.channel.MessageExamplesList import org.openfolder.kotlinasyncapi.model.channel.MessageTraitsList -internal val objectMapper = ObjectMapper() -internal val jsonSchemaGenerator = JsonSchemaGenerator(objectMapper) - internal fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Message = org.openfolder.kotlinasyncapi.model.channel.Message().apply { messageId = this@toMessage.messageId.takeIf { it.isNotEmpty() } @@ -28,19 +25,34 @@ internal fun Message.toMessage(): org.openfolder.kotlinasyncapi.model.channel.Me summary = this@toMessage.summary.takeIf { it.isNotEmpty() } description = this@toMessage.description.takeIf { it.isNotEmpty() } correlationId = this@toMessage.correlationId.takeUnless { it.default }?.toCorrelationID() - tags = this@toMessage.tags.map { it.toTag() }.toList() as TagsList + tags = this@toMessage.tags.takeUnless { it.isEmpty() }?.toTagsList() externalDocs = this@toMessage.externalDocs.takeUnless { it.default }?.toExternalDocumentation() - examples = this@toMessage.examples.map { it.toMessageExample() }.toList() as MessageExamplesList + examples = this@toMessage.examples.takeUnless { it.isEmpty() }?.toMessageExamplesList() bindings = this@toMessage.bindings.takeUnless { it.default }?.toMessageBindings() - traits = this@toMessage.traits.map { it.toMessageTrait() }.toList() as MessageTraitsList + traits = this@toMessage.traits.takeUnless { it.isEmpty() }?.toMessageTraitsList() headers = this@toMessage.headers.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } payload = this@toMessage.payload.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } } +internal fun Array.toTagsList(): TagsList = + TagsList().apply { + addAll(this@toTagsList.map { it.toTag() }) + } + +internal fun Array.toMessageExamplesList(): MessageExamplesList = + MessageExamplesList().apply { + addAll(this@toMessageExamplesList.map { it.toMessageExample() }) + } + +internal fun Array.toMessageTraitsList(): MessageTraitsList = + MessageTraitsList().apply { + addAll(this@toMessageTraitsList.map { it.toMessageTrait() }) + } + internal fun CorrelationID.toCorrelationID(): org.openfolder.kotlinasyncapi.model.CorrelationID = org.openfolder.kotlinasyncapi.model.CorrelationID().apply { location = this@toCorrelationID.location @@ -62,6 +74,8 @@ internal fun Tag.toTag(): org.openfolder.kotlinasyncapi.model.Tag = internal fun MessageExample.toMessageExample(): org.openfolder.kotlinasyncapi.model.channel.MessageExample = org.openfolder.kotlinasyncapi.model.channel.MessageExample().apply { + val objectMapper = ObjectMapper() + name = this@toMessageExample.name.takeIf { it.isNotEmpty() } summary = this@toMessageExample.summary.takeIf { it.isNotEmpty() } payload = this@toMessageExample.payload.takeIf { it.isNotEmpty() }?.let { @@ -92,12 +106,12 @@ internal fun MessageTrait.toMessageTrait(): org.openfolder.kotlinasyncapi.model. summary = this@toMessageTrait.summary.takeIf { it.isNotEmpty() } description = this@toMessageTrait.description.takeIf { it.isNotEmpty() } correlationId = this@toMessageTrait.correlationId.takeUnless { it.default }?.toCorrelationID() - tags = this@toMessageTrait.tags.map { it.toTag() }.toList() as TagsList + tags = this@toMessageTrait.tags.takeUnless { it.isEmpty() }?.toTagsList() externalDocs = this@toMessageTrait.externalDocs.takeUnless { it.default }?.toExternalDocumentation() - examples = this@toMessageTrait.examples.map { it.toMessageExample() }.toList() as MessageExamplesList + examples = this@toMessageTrait.examples.takeUnless { it.isEmpty() }?.toMessageExamplesList() bindings = this@toMessageTrait.bindings.takeUnless { it.default }?.toMessageBindings() headers = this@toMessageTrait.headers.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } } @@ -105,7 +119,7 @@ internal fun MessageBinding.HTTP.toHTTP(): org.openfolder.kotlinasyncapi.model.c org.openfolder.kotlinasyncapi.model.channel.MessageBinding.HTTP().apply { bindingVersion = this@toHTTP.bindingVersion.takeIf { it.isNotEmpty() } headers = this@toHTTP.headers.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } } @@ -113,7 +127,7 @@ internal fun MessageBinding.Kafka.toKafka(): org.openfolder.kotlinasyncapi.model org.openfolder.kotlinasyncapi.model.channel.MessageBinding.Kafka().apply { bindingVersion = this@toKafka.bindingVersion.takeIf { it.isNotEmpty() } key = this@toKafka.key.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } } @@ -121,7 +135,7 @@ internal fun MessageBinding.AnypointMQ.toAnypointMQ(): org.openfolder.kotlinasyn org.openfolder.kotlinasyncapi.model.channel.MessageBinding.AnypointMQ().apply { bindingVersion = this@toAnypointMQ.bindingVersion.takeIf { it.isNotEmpty() } headers = this@toAnypointMQ.headers.takeUnless { it.default }?.implementation?.let { - jsonSchemaGenerator.generateSchema(it.java) + Reference().apply { ref("#/components/schemas/${it.simpleName}") } } } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt index a877022..5c88d6d 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt @@ -1,26 +1,33 @@ package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator +import io.swagger.v3.core.converter.ModelConverters import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.model.Reference import org.openfolder.kotlinasyncapi.model.component.Components import org.springframework.stereotype.Component import kotlin.reflect.KClass @Component internal class MessageProcessor: AnnotationProcessor { - override fun process(annotation: Message, clazz: KClass<*>?): Components { - val objectMapper = ObjectMapper() - val schemaGenerator = JsonSchemaGenerator(objectMapper) - val jsonSchema = clazz?.let { - schemaGenerator.generateSchema(it.java) - } + override fun process(annotation: Message, clazz: KClass<*>): Components { + val converters = ModelConverters() + val jsonSchema = converters.readAll(clazz.java) return Components().apply { messages { annotation.toMessage() - .apply { payload = payload ?: jsonSchema } - .let { put(clazz.toString(), it) } + .apply { + payload = payload ?: Reference().apply { + ref("#/components/schemas/${clazz.simpleName}") + } + schemaFormat = "application/schema+json;version=draft-07" + } + .also { + put(clazz.java.simpleName, it) + } + } + schemas { + putAll(jsonSchema) } } } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt new file mode 100644 index 0000000..ac40fe3 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt @@ -0,0 +1,21 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import io.swagger.v3.core.converter.ModelConverters +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.model.component.Components +import org.springframework.stereotype.Component +import kotlin.reflect.KClass + +@Component +internal class SchemaProcessor : AnnotationProcessor { + override fun process(annotation: Schema, clazz: KClass<*>): Components { + val converters = ModelConverters() + val jsonSchema = converters.readAll(clazz.java) + + return Components().apply { + schemas { + putAll(jsonSchema) + } + } + } +} diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt new file mode 100644 index 0000000..da13f9a --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt @@ -0,0 +1,42 @@ +package org.openfolder.kotlinasyncapi.springweb.context + +import org.junit.jupiter.api.Test + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringBootConfiguration +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +internal class DefaultAnnotationProviderTest { + + @Test + fun `should provide annotation components`() { + + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @EnableAsyncApi + open class TestConfig + + @Message( + messageId = "testMessageId", + name = "testName", + description = "testDescription", + headers = Schema(implementation = TestHeaders::class) + ) + data class TestMessage( + val id: Int = 0, + val name: String, + val isTest: Boolean + ) + + @Schema + data class TestHeaders( + val type: String + ) +} diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt new file mode 100644 index 0000000..30603f6 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt @@ -0,0 +1,43 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import org.junit.jupiter.api.Test + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.Tag +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.springweb.TestUtils +import kotlin.reflect.full.findAnnotation + +internal class MessageProcessorTest { + + private val processor = MessageProcessor() + + @Test + fun `should process message annotation`() { + val payload = TestPayload::class + val annotation = payload.findAnnotation()!! + + val expected = TestUtils.json("annotation/message_component.json") + val actual = TestUtils.json(processor.process(annotation, payload)) + + TestUtils.assertJsonEquals(expected, actual) + } + + @Message( + messageId = "testMessageId", + name = "testName", + description = "testDescription", + tags = [Tag(name = "testName")], + headers = Schema(implementation = TestHeaders::class) + ) + data class TestPayload( + val id: Int = 0, + val name: String, + val isTest: Boolean, + ) + + @Schema + data class TestHeaders( + val TYPE: String + ) +} diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt new file mode 100644 index 0000000..6dcfc8f --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt @@ -0,0 +1,35 @@ +package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor + +import org.junit.jupiter.api.Test + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.springweb.TestUtils +import kotlin.reflect.full.findAnnotation + +internal class SchemaProcessorTest { + + private val processor = SchemaProcessor() + + @Test + fun `should process schema annotation`() { + val payload = TestSchema::class + val annotation = payload.findAnnotation()!! + + val expected = TestUtils.json("annotation/schema_component.json") + val actual = TestUtils.json(processor.process(annotation, payload)) + + TestUtils.assertJsonEquals(expected, actual) + } + + @Schema + data class TestSchema( + val id: Int = 0, + val name: String, + val isTest: Boolean, + val sub: TestSubSchema + ) + + data class TestSubSchema( + val exists: Boolean + ) +} diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/controller/AsyncApiControllerIntegrationTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/controller/AsyncApiControllerIntegrationTest.kt index b886aa5..053ac5c 100644 --- a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/controller/AsyncApiControllerIntegrationTest.kt +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/controller/AsyncApiControllerIntegrationTest.kt @@ -104,10 +104,8 @@ internal class AsyncApiControllerIntegrationTest { } } } - bindings { - reference("http") { - ref("#/components/messageBindings/streamingHeaders") - } + bindingsRef { + ref("#/components/messageBindings/streamingHeaders") } } message("heartbeat") { @@ -117,10 +115,8 @@ internal class AsyncApiControllerIntegrationTest { type("string") enum("\r\n") } - bindings { - reference("http") { - ref("#/components/messageBindings/streamingHeaders") - } + bindingsRef { + ref("#/components/messageBindings/streamingHeaders") } } } diff --git a/kotlin-asyncapi-spring-web/src/test/resources/annotation/message_component.json b/kotlin-asyncapi-spring-web/src/test/resources/annotation/message_component.json new file mode 100644 index 0000000..880c5cf --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/resources/annotation/message_component.json @@ -0,0 +1,43 @@ +{ + "messages" : { + "TestPayload" : { + "messageId" : "testMessageId", + "headers" : { + "$ref" : "#/components/schemas/TestHeaders" + }, + "payload" : { + "$ref" : "#/components/schemas/TestPayload" + }, + "schemaFormat" : "application/schema+json;version=draft-07", + "name" : "testName", + "description" : "testDescription", + "tags" : [ { + "name" : "testName" + } ] + } + }, + "schemas" : { + "TestPayload" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32", + "exampleSetFlag" : false, + "types" : [ "integer" ] + }, + "name" : { + "type" : "string", + "exampleSetFlag" : false, + "types" : [ "string" ] + }, + "test" : { + "type" : "boolean", + "exampleSetFlag" : false, + "types" : [ "boolean" ] + } + }, + "exampleSetFlag" : false + } + } +} \ No newline at end of file diff --git a/kotlin-asyncapi-spring-web/src/test/resources/annotation/schema_component.json b/kotlin-asyncapi-spring-web/src/test/resources/annotation/schema_component.json new file mode 100644 index 0000000..4145537 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/resources/annotation/schema_component.json @@ -0,0 +1,41 @@ +{ + "schemas" : { + "TestSchema" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32", + "exampleSetFlag" : false, + "types" : [ "integer" ] + }, + "name" : { + "type" : "string", + "exampleSetFlag" : false, + "types" : [ "string" ] + }, + "sub" : { + "$ref" : "#/components/schemas/TestSubSchema", + "exampleSetFlag" : false + }, + "test" : { + "type" : "boolean", + "exampleSetFlag" : false, + "types" : [ "boolean" ] + } + }, + "exampleSetFlag" : false + }, + "TestSubSchema" : { + "type" : "object", + "properties" : { + "exists" : { + "type" : "boolean", + "exampleSetFlag" : false, + "types" : [ "boolean" ] + } + }, + "exampleSetFlag" : false + } + } +} diff --git a/kotlin-asyncapi-spring-web/src/test/resources/async_api_integration.json b/kotlin-asyncapi-spring-web/src/test/resources/async_api_integration.json index e4cc042..e42422c 100644 --- a/kotlin-asyncapi-spring-web/src/test/resources/async_api_integration.json +++ b/kotlin-asyncapi-spring-web/src/test/resources/async_api_integration.json @@ -62,9 +62,7 @@ "schemaFormat" : "application/schema+yaml;version=draft-07", "summary" : "A message represents an individual chat message sent to a room.", "bindings" : { - "http" : { - "$ref" : "#/components/messageBindings/streamingHeaders" - } + "$ref" : "#/components/messageBindings/streamingHeaders" } }, "heartbeat" : { @@ -75,9 +73,7 @@ "schemaFormat" : "application/schema+yaml;version=draft-07", "summary" : "Its purpose is to keep the connection alive.", "bindings" : { - "http" : { - "$ref" : "#/components/messageBindings/streamingHeaders" - } + "$ref" : "#/components/messageBindings/streamingHeaders" } } }, diff --git a/pom.xml b/pom.xml index dc877ea..a72ae87 100644 --- a/pom.xml +++ b/pom.xml @@ -243,12 +243,7 @@ com.fasterxml.jackson.core jackson-databind - 2.14.1 - - - com.fasterxml.jackson.module - jackson-module-jsonSchema - 2.14.1 + 2.14.2 org.jetbrains.kotlin From ea90fbd2e9cb5f5f0a38943c0bf5c02523a7db09 Mon Sep 17 00:00:00 2001 From: lorenzsimon Date: Sun, 19 Mar 2023 10:42:19 +0200 Subject: [PATCH 4/6] refactor - Make annotation processor context dynamic test - Add annotation provider integration test --- .../springweb/AsyncApiAutoConfiguration.kt | 36 +++++++- .../springweb/context/AnnotationProvider.kt | 11 ++- .../processor/AnnotationProcessor.kt | 5 +- .../annotation/processor/MessageProcessor.kt | 10 +-- .../annotation/processor/SchemaProcessor.kt | 6 +- .../context/DefaultAnnotationProviderTest.kt | 42 ---------- .../external/DefaultAnnotationProviderTest.kt | 59 +++++++++++++ .../annotation/annotation_integration.json | 82 +++++++++++++++++++ 8 files changed, 193 insertions(+), 58 deletions(-) delete mode 100644 kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt create mode 100644 kotlin-asyncapi-spring-web/src/test/resources/annotation/annotation_integration.json diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/AsyncApiAutoConfiguration.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/AsyncApiAutoConfiguration.kt index a04e663..53ab285 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/AsyncApiAutoConfiguration.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/AsyncApiAutoConfiguration.kt @@ -1,9 +1,17 @@ package org.openfolder.kotlinasyncapi.springweb +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.channel.Message import org.openfolder.kotlinasyncapi.model.AsyncApi +import org.openfolder.kotlinasyncapi.springweb.context.DefaultAnnotationProvider import org.openfolder.kotlinasyncapi.springweb.context.DefaultInfoProvider import org.openfolder.kotlinasyncapi.springweb.context.DefaultResourceProvider import org.openfolder.kotlinasyncapi.springweb.context.ResourceProvider +import org.openfolder.kotlinasyncapi.springweb.context.annotation.AnnotationScanner +import org.openfolder.kotlinasyncapi.springweb.context.annotation.DefaultAnnotationScanner +import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.AnnotationProcessor +import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.MessageProcessor +import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.SchemaProcessor import org.openfolder.kotlinasyncapi.springweb.controller.AsyncApiController import org.openfolder.kotlinasyncapi.springweb.service.AsyncApiExtension import org.openfolder.kotlinasyncapi.springweb.service.AsyncApiSerializer @@ -19,12 +27,13 @@ import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import +import kotlin.reflect.KClass import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost @Configuration @ConditionalOnBean(AsyncApiMarkerConfiguration.Marker::class) -@Import(AsyncApiScriptAutoConfiguration::class) +@Import(AsyncApiScriptAutoConfiguration::class, AsyncApiAnnotationAutoConfiguration::class) internal open class AsyncApiAutoConfiguration { @Bean @@ -81,6 +90,31 @@ internal open class AsyncApiScriptAutoConfiguration { } } +@Configuration +@ConditionalOnProperty(name = ["asyncapi.annotation.enabled"], havingValue = "true", matchIfMissing = true) +internal open class AsyncApiAnnotationAutoConfiguration { + + @Bean + open fun messageProcessor() = + MessageProcessor() + + @Bean + open fun schemaProcessor() = + SchemaProcessor() + + @Bean + open fun annotationScanner(context: ApplicationContext) = + DefaultAnnotationScanner(context) + + @Bean + open fun annotationProvider( + context: ApplicationContext, + scanner: AnnotationScanner, + messageProcessor: AnnotationProcessor>, + schemaProcessor: AnnotationProcessor> + ) = DefaultAnnotationProvider(context, scanner, messageProcessor, schemaProcessor) +} + @Configuration @ConditionalOnClass(BasicJvmScriptingHost::class) internal open class AsyncApiEmbeddedScriptAutoConfiguration { diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt index e80569e..0d02aab 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/AnnotationProvider.kt @@ -23,6 +23,7 @@ import org.openfolder.kotlinasyncapi.springweb.context.annotation.AnnotationScan import org.openfolder.kotlinasyncapi.springweb.context.annotation.processor.AnnotationProcessor import org.springframework.context.ApplicationContext import org.springframework.stereotype.Component +import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation internal interface AnnotationProvider { @@ -34,17 +35,19 @@ internal interface AnnotationProvider { internal class DefaultAnnotationProvider( context: ApplicationContext, scanner: AnnotationScanner, - messageProcessor: AnnotationProcessor, - schemaProcessor: AnnotationProcessor + messageProcessor: AnnotationProcessor>, + schemaProcessor: AnnotationProcessor> ) : AnnotationProvider { override val components: Components? by lazy { val scanPackage = context.getBeansWithAnnotation(EnableAsyncApi::class.java).values .firstOrNull() - ?.let { it::class.java.packageName } + ?.let { it::class.java.`package`.name } ?.takeIf { it.isNotEmpty() } - val annotatedClasses = scanner.scan(scanPackage = scanPackage!!, annotation = AsyncApiAnnotation::class) + val annotatedClasses = scanPackage?.let { + scanner.scan(scanPackage = it, annotation = AsyncApiAnnotation::class) + } ?: emptyList() Components().apply { annotatedClasses diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt index 03f03fa..dac72ab 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/AnnotationProcessor.kt @@ -1,8 +1,7 @@ package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor import org.openfolder.kotlinasyncapi.model.component.Components -import kotlin.reflect.KClass -internal interface AnnotationProcessor { - fun process(annotation: T, clazz: KClass<*>): Components +internal interface AnnotationProcessor { + fun process(annotation: T, context: U): Components } diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt index 5c88d6d..72f3f6e 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt @@ -8,22 +8,22 @@ import org.springframework.stereotype.Component import kotlin.reflect.KClass @Component -internal class MessageProcessor: AnnotationProcessor { - override fun process(annotation: Message, clazz: KClass<*>): Components { +internal class MessageProcessor: AnnotationProcessor> { + override fun process(annotation: Message, context: KClass<*>): Components { val converters = ModelConverters() - val jsonSchema = converters.readAll(clazz.java) + val jsonSchema = converters.readAll(context.java) return Components().apply { messages { annotation.toMessage() .apply { payload = payload ?: Reference().apply { - ref("#/components/schemas/${clazz.simpleName}") + ref("#/components/schemas/${context.simpleName}") } schemaFormat = "application/schema+json;version=draft-07" } .also { - put(clazz.java.simpleName, it) + put(context.java.simpleName, it) } } schemas { diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt index ac40fe3..7873e2e 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessor.kt @@ -7,10 +7,10 @@ import org.springframework.stereotype.Component import kotlin.reflect.KClass @Component -internal class SchemaProcessor : AnnotationProcessor { - override fun process(annotation: Schema, clazz: KClass<*>): Components { +internal class SchemaProcessor : AnnotationProcessor> { + override fun process(annotation: Schema, context: KClass<*>): Components { val converters = ModelConverters() - val jsonSchema = converters.readAll(clazz.java) + val jsonSchema = converters.readAll(context.java) return Components().apply { schemas { diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt deleted file mode 100644 index da13f9a..0000000 --- a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/DefaultAnnotationProviderTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.openfolder.kotlinasyncapi.springweb.context - -import org.junit.jupiter.api.Test - -import org.openfolder.kotlinasyncapi.annotation.Schema -import org.openfolder.kotlinasyncapi.annotation.channel.Message -import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -internal class DefaultAnnotationProviderTest { - - @Test - fun `should provide annotation components`() { - - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @EnableAsyncApi - open class TestConfig - - @Message( - messageId = "testMessageId", - name = "testName", - description = "testDescription", - headers = Schema(implementation = TestHeaders::class) - ) - data class TestMessage( - val id: Int = 0, - val name: String, - val isTest: Boolean - ) - - @Schema - data class TestHeaders( - val type: String - ) -} diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt new file mode 100644 index 0000000..5b1b64f --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt @@ -0,0 +1,59 @@ +package org.openfolder.kotlinasyncapi.springweb.context.external + +import org.junit.jupiter.api.Test +import org.openfolder.kotlinasyncapi.annotation.ExternalDocumentation + +import org.openfolder.kotlinasyncapi.annotation.Schema +import org.openfolder.kotlinasyncapi.annotation.Tag +import org.openfolder.kotlinasyncapi.annotation.channel.Message +import org.openfolder.kotlinasyncapi.annotation.channel.MessageBinding +import org.openfolder.kotlinasyncapi.annotation.channel.MessageBindings +import org.openfolder.kotlinasyncapi.annotation.channel.MessageExample +import org.openfolder.kotlinasyncapi.springweb.EnableAsyncApi +import org.openfolder.kotlinasyncapi.springweb.TestUtils +import org.openfolder.kotlinasyncapi.springweb.context.DefaultAnnotationProvider +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringBootConfiguration +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +internal class DefaultAnnotationProviderTest { + + @Autowired + private lateinit var annotationProvider: DefaultAnnotationProvider + + @Test + fun `should provide annotation components`() { + val expected = TestUtils.json("annotation/annotation_integration.json") + val actual = TestUtils.json(annotationProvider.components!!) + + TestUtils.assertJsonEquals(expected, actual) + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @EnableAsyncApi + open class TestConfig + + @Message( + messageId = "testMessageId", + name = "testName", + description = "testDescription", + headers = Schema(implementation = TestHeaders::class), + externalDocs = ExternalDocumentation(url = "http://example.com"), + bindings = MessageBindings(http = MessageBinding.HTTP(bindingVersion = "1.0")), + examples = [MessageExample(headers = "{\"type\":\"TEST\"}", payload = "{\"body\":\"body test\"}")], + tags = [Tag(name = "testName")] + ) + data class TestMessage( + val id: Int = 0, + val name: String, + val isTest: Boolean + ) + + @Schema + data class TestHeaders( + val type: String + ) +} diff --git a/kotlin-asyncapi-spring-web/src/test/resources/annotation/annotation_integration.json b/kotlin-asyncapi-spring-web/src/test/resources/annotation/annotation_integration.json new file mode 100644 index 0000000..0f8ea03 --- /dev/null +++ b/kotlin-asyncapi-spring-web/src/test/resources/annotation/annotation_integration.json @@ -0,0 +1,82 @@ +{ + "messages": { + "TestMessage": { + "messageId": "testMessageId", + "headers": { + "$ref": "#/components/schemas/TestHeaders" + }, + "payload": { + "$ref": "#/components/schemas/TestMessage" + }, + "schemaFormat": "application/schema+json;version=draft-07", + "name": "testName", + "description": "testDescription", + "tags": [ + { + "name": "testName" + } + ], + "externalDocs": { + "url": "http://example.com" + }, + "bindings": { + "http": { + "bindingVersion": "1.0" + } + }, + "examples": [ + { + "headers": { + "type": "TEST" + }, + "payload": { + "body": "body test" + } + } + ] + } + }, + "schemas": { + "TestHeaders": { + "type": "object", + "properties": { + "type": { + "type": "string", + "exampleSetFlag": false, + "types": [ + "string" + ] + } + }, + "exampleSetFlag": false + }, + "TestMessage": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "exampleSetFlag": false, + "types": [ + "integer" + ] + }, + "name": { + "type": "string", + "exampleSetFlag": false, + "types": [ + "string" + ] + }, + "test": { + "type": "boolean", + "exampleSetFlag": false, + "types": [ + "boolean" + ] + } + }, + "exampleSetFlag": false + } + } +} From 9c1977c208c209b70ebbce0adca3a8bb82710617 Mon Sep 17 00:00:00 2001 From: lorenzsimon Date: Sun, 19 Mar 2023 10:46:45 +0200 Subject: [PATCH 5/6] chore - ktlint format --- .../annotation/{AsyncApi.kt => AsyncApiAnnotation.kt} | 0 .../kotlinasyncapi/annotation/channel/MessageExample.kt | 2 -- .../springweb/context/annotation/AnnotationScanner.kt | 2 +- .../springweb/context/annotation/processor/MessageProcessor.kt | 2 +- .../context/annotation/processor/MessageProcessorTest.kt | 3 +-- .../context/annotation/processor/SchemaProcessorTest.kt | 1 - .../context/external/DefaultAnnotationProviderTest.kt | 1 - 7 files changed, 3 insertions(+), 8 deletions(-) rename kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/{AsyncApi.kt => AsyncApiAnnotation.kt} (100%) diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApiAnnotation.kt similarity index 100% rename from kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApi.kt rename to kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/AsyncApiAnnotation.kt diff --git a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt index 49d1cd4..cff4ee8 100644 --- a/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt +++ b/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/channel/MessageExample.kt @@ -1,7 +1,5 @@ package org.openfolder.kotlinasyncapi.annotation.channel -import org.openfolder.kotlinasyncapi.annotation.Schema - annotation class MessageExample( val default: Boolean = false, val headers: String = "", diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt index 3fc5cb5..8b4c969 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/AnnotationScanner.kt @@ -14,7 +14,7 @@ internal interface AnnotationScanner { @Component internal class DefaultAnnotationScanner( private val context: ApplicationContext -): AnnotationScanner { +) : AnnotationScanner { override fun scan(scanPackage: String, annotation: KClass): List> { val classPathScanner = ClassPathScanningCandidateComponentProvider(false).also { it.addIncludeFilter(AnnotationTypeFilter(AsyncApiAnnotation::class.java)) diff --git a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt index 72f3f6e..5b945a5 100644 --- a/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt +++ b/kotlin-asyncapi-spring-web/src/main/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessor.kt @@ -8,7 +8,7 @@ import org.springframework.stereotype.Component import kotlin.reflect.KClass @Component -internal class MessageProcessor: AnnotationProcessor> { +internal class MessageProcessor : AnnotationProcessor> { override fun process(annotation: Message, context: KClass<*>): Components { val converters = ModelConverters() val jsonSchema = converters.readAll(context.java) diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt index 30603f6..f05f25c 100644 --- a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/MessageProcessorTest.kt @@ -1,7 +1,6 @@ package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor import org.junit.jupiter.api.Test - import org.openfolder.kotlinasyncapi.annotation.Schema import org.openfolder.kotlinasyncapi.annotation.Tag import org.openfolder.kotlinasyncapi.annotation.channel.Message @@ -33,7 +32,7 @@ internal class MessageProcessorTest { data class TestPayload( val id: Int = 0, val name: String, - val isTest: Boolean, + val isTest: Boolean ) @Schema diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt index 6dcfc8f..fe9e264 100644 --- a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/annotation/processor/SchemaProcessorTest.kt @@ -1,7 +1,6 @@ package org.openfolder.kotlinasyncapi.springweb.context.annotation.processor import org.junit.jupiter.api.Test - import org.openfolder.kotlinasyncapi.annotation.Schema import org.openfolder.kotlinasyncapi.springweb.TestUtils import kotlin.reflect.full.findAnnotation diff --git a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt index 5b1b64f..920cd30 100644 --- a/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt +++ b/kotlin-asyncapi-spring-web/src/test/kotlin/org/openfolder/kotlinasyncapi/springweb/context/external/DefaultAnnotationProviderTest.kt @@ -2,7 +2,6 @@ package org.openfolder.kotlinasyncapi.springweb.context.external import org.junit.jupiter.api.Test import org.openfolder.kotlinasyncapi.annotation.ExternalDocumentation - import org.openfolder.kotlinasyncapi.annotation.Schema import org.openfolder.kotlinasyncapi.annotation.Tag import org.openfolder.kotlinasyncapi.annotation.channel.Message From 7951f639aea520a9ac5b7eb864a875674c8a4f37 Mon Sep 17 00:00:00 2001 From: Lorenz Simon <95355196+lorenz-scalable@users.noreply.github.com> Date: Mon, 20 Mar 2023 10:25:24 +0200 Subject: [PATCH 6/6] refactor - Refactor dependency management --- kotlin-asyncapi-annotation/pom.xml | 7 ------- kotlin-asyncapi-spring-web/pom.xml | 1 - pom.xml | 5 +++++ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/kotlin-asyncapi-annotation/pom.xml b/kotlin-asyncapi-annotation/pom.xml index 479de8d..d0d78b5 100644 --- a/kotlin-asyncapi-annotation/pom.xml +++ b/kotlin-asyncapi-annotation/pom.xml @@ -13,11 +13,4 @@ Kotlin AsyncAPI Annotation AsyncAPI annotations for meta-documentation - - - - org.openfolder - kotlin-asyncapi-core - - diff --git a/kotlin-asyncapi-spring-web/pom.xml b/kotlin-asyncapi-spring-web/pom.xml index 56823ce..fe6647b 100644 --- a/kotlin-asyncapi-spring-web/pom.xml +++ b/kotlin-asyncapi-spring-web/pom.xml @@ -34,7 +34,6 @@ io.swagger.core.v3 swagger-core - 2.2.8 org.springframework diff --git a/pom.xml b/pom.xml index a72ae87..de49df0 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,11 @@ jackson-databind 2.14.2 + + io.swagger.core.v3 + swagger-core + 2.2.8 + org.jetbrains.kotlin kotlin-scripting-jvm-host