From d5e36425f1abaf4b6e057c8acaefcd504bd74223 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Mon, 15 Jun 2026 22:41:07 +0200 Subject: [PATCH 01/10] delete spring-kafka 'full' mode + tests and templates --- README.md | 14 +- .../banking/asyncapi/generator/cli/Main.kt | 16 -- .../generator/cli/AsyncApiGeneratorCliTest.kt | 19 -- .../configuration/ClientGeneration.kt | 4 - .../GeneratorConfigurationFactory.kt | 6 - .../GeneratorConfigurationRequest.kt | 17 +- .../factory/JavaKafkaGeneratorModelFactory.kt | 158 ------------ ...tory.kt => JavaSpringKafkaModelFactory.kt} | 2 +- ...vaSpringKafkaAutoConfigurationGenerator.kt | 24 -- ...kt => JavaSpringKafkaConsumerGenerator.kt} | 4 +- .../kafka/spring/JavaSpringKafkaGenerator.kt | 38 +-- .../spring/JavaSpringKafkaHandlerGenerator.kt | 23 -- .../JavaSpringKafkaListenerGenerator.kt | 23 -- .../JavaSpringKafkaProducerGenerator.kt | 5 +- .../spring/JavaSpringKafkaSimpleGenerator.kt | 29 --- .../JavaSpringKafkaSimpleProducerGenerator.kt | 24 -- .../kafka/spring/AutoConfigurationModel.kt | 7 - .../AutoConfigurationResourceGenerator.kt | 15 -- .../spring/NativeKafkaPayloadResolver.kt | 4 +- .../spring/SpringKafkaClientGeneration.kt | 67 ++--- .../KotlinKafkaGeneratorModelFactory.kt | 131 ---------- ...ry.kt => KotlinSpringKafkaModelFactory.kt} | 2 +- ...inSpringKafkaAutoConfigurationGenerator.kt | 24 -- ... => KotlinSpringKafkaConsumerGenerator.kt} | 4 +- .../spring/KotlinSpringKafkaGenerator.kt | 36 +-- .../KotlinSpringKafkaListenerGenerator.kt | 24 -- ...otlinSpringKafkaSimpleConsumerGenerator.kt | 24 -- .../KotlinSpringKafkaSimpleGenerator.kt | 29 --- ...otlinSpringKafkaSimpleProducerGenerator.kt | 24 -- .../core/generator/plan/GenerationPlanner.kt | 20 +- .../core/generator/plan/GenerationTask.kt | 2 - .../generator/plan/SpringKafkaClientType.kt | 37 --- .../spring-kafka-autoconfiguration.mustache | 9 - ...ustache => spring-kafka-consumer.mustache} | 0 .../java/spring-kafka-handler.mustache | 16 -- .../java/spring-kafka-listener.mustache | 36 --- .../java/spring-kafka-producer.mustache | 19 +- .../spring-kafka-simple-producer.mustache | 27 -- .../spring-kafka-autoconfiguration.mustache | 8 - ...ustache => spring-kafka-consumer.mustache} | 0 .../kotlin/spring-kafka-handler.mustache | 16 -- .../kotlin/spring-kafka-listener.mustache | 32 --- .../kotlin/spring-kafka-producer.mustache | 17 +- .../spring-kafka-simple-producer.mustache | 21 -- .../generator/AbstractJavaGeneratorClass.kt | 5 - .../generator/AbstractKotlinGeneratorClass.kt | 5 - .../AsyncApiGeneratorOutputContractTest.kt | 2 - .../GeneratorConfigurationFactoryTest.kt | 29 +-- .../GeneratorConfigurationRequestTest.kt | 12 +- ...nerationInputCompatibilityValidatorTest.kt | 7 - .../kafka/GenerateJavaPrimitivePayloadTest.kt | 10 +- .../GenerateJavaSpringKafkaClientTest.kt | 237 ------------------ ...ateJavaSpringKafkaOpenPayloadClientTest.kt | 36 --- ...Test.kt => GenerateJavaSpringKafkaTest.kt} | 32 +-- .../spring/SpringKafkaClientGenerationTest.kt | 89 +------ ...eKotlinSpringKafkaOpenPayloadClientTest.kt | 43 +--- ...GenerateKotlinSpringKafkaOperationsTest.kt | 32 +-- .../GenerateKotlinSpringKafkaSimpleTest.kt | 171 ------------- .../kafka/GenerateKotlinSpringKafkaTest.kt | 158 ++++-------- .../kafka/GeneratePrimitivePayloadTest.kt | 12 +- .../generator/plan/GenerationPlannerTest.kt | 106 +------- .../plan/SpringKafkaClientTypeTest.kt | 52 ---- .../generator/gradle/plugin/AsyncApiPlugin.kt | 2 - .../plugin/extensions/AsyncApiExtension.kt | 2 - .../plugin/tasks/GenerateAsyncApiTask.kt | 18 -- .../gradle/plugin/AsyncApiPluginTest.kt | 30 --- .../plugin/MavenGeneratorConfiguration.kt | 4 - .../src/test/it/config-merge/pom.xml | 1 - .../src/test/it/spring-kafka-simple/pom.xml | 1 - .../maven/plugin/AsyncApiGeneratorMojoTest.kt | 16 -- .../generator/maven/plugin/MavenTestHelper.kt | 4 - 71 files changed, 136 insertions(+), 2037 deletions(-) delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaKafkaGeneratorModelFactory.kt rename asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/{JavaSpringKafkaSimpleModelFactory.kt => JavaSpringKafkaModelFactory.kt} (99%) delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaAutoConfigurationGenerator.kt rename asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/{JavaSpringKafkaSimpleConsumerGenerator.kt => JavaSpringKafkaConsumerGenerator.kt} (85%) delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaHandlerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaListenerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleProducerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationModel.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationResourceGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinKafkaGeneratorModelFactory.kt rename asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/{KotlinSpringKafkaSimpleModelFactory.kt => KotlinSpringKafkaModelFactory.kt} (99%) delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaAutoConfigurationGenerator.kt rename asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/{KotlinSpringKafkaHandlerGenerator.kt => KotlinSpringKafkaConsumerGenerator.kt} (86%) delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaListenerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleConsumerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleProducerGenerator.kt delete mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientType.kt delete mode 100644 asyncapi-generator-core/src/main/resources/java/spring-kafka-autoconfiguration.mustache rename asyncapi-generator-core/src/main/resources/java/{spring-kafka-simple-consumer.mustache => spring-kafka-consumer.mustache} (100%) delete mode 100644 asyncapi-generator-core/src/main/resources/java/spring-kafka-handler.mustache delete mode 100644 asyncapi-generator-core/src/main/resources/java/spring-kafka-listener.mustache delete mode 100644 asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-producer.mustache delete mode 100644 asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-autoconfiguration.mustache rename asyncapi-generator-core/src/main/resources/kotlin/{spring-kafka-simple-consumer.mustache => spring-kafka-consumer.mustache} (100%) delete mode 100644 asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-handler.mustache delete mode 100644 asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-listener.mustache delete mode 100644 asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-producer.mustache delete mode 100644 asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaClientTest.kt rename asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/{GenerateJavaSpringKafkaSimpleTest.kt => GenerateJavaSpringKafkaTest.kt} (82%) delete mode 100644 asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaSimpleTest.kt delete mode 100644 asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientTypeTest.kt diff --git a/README.md b/README.md index 778f7696..cc6e48a8 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ Example usage in your `pom.xml`: my.package.path.client my.package.path.model - simple @@ -82,7 +81,6 @@ asyncapiGenerate { packageName.set("my.package.path.client") // Optional: defaults to models.packageName when models are configured modelPackageName.set("my.package.path.model") - mode.set("simple") // options: full, simple - default simple } } schemas { @@ -115,7 +113,6 @@ asyncapiGenerate { packageName = 'my.package.path.client' // Optional: defaults to models.packageName when models are configured modelPackageName = 'my.package.path.model' - mode = 'simple' // options: full, simple - default simple } } schemas { @@ -305,20 +302,15 @@ Spring Kafka output is configured under `clients.springKafka`. Generated Spring Kafka clients use `models.packageName` for payload model types by default. If models are generated elsewhere, configure `clients.springKafka.modelPackageName` to point the client API at that package without generating model output in the same execution. -For native Avro message payloads, generated Spring Kafka clients use the Java type declared by the Avro schema namespace and name. For example, a native Avro schema with `namespace: com.example.avro` and `name: UserCreated` is used as `com.example.avro.UserCreated` in generated producer, consumer, listener, and handler APIs. +For native Avro message payloads, generated Spring Kafka clients use the Java type declared by the Avro schema namespace and name. For example, a native Avro schema with `namespace: com.example.avro` and `name: UserCreated` is used as `com.example.avro.UserCreated` in generated producer and consumer APIs. For native Protobuf message payloads, generated Spring Kafka clients use the Java type declared by `option java_package`, or by the Protobuf `package` when `java_package` is omitted. Protobuf client generation requires `option java_multiple_files = true;` so the generated message can be referenced as a top-level Java type. The `.proto` schema must contain a top-level message matching the payload name. The generator does not configure Kafka Avro or Protobuf serializers and deserializers yet; applications still own that runtime wiring. -The current generator has two modes: +Generated Spring Kafka clients are contract-only source artifacts. Producer-oriented channels generate producer wrappers around application-provided `KafkaTemplate` instances. Consumer-oriented channels generate consumer interfaces that receive typed `ConsumerRecord` values. The generator does not create Spring Boot auto-configuration, `@KafkaListener` classes, listener containers, serializer configuration, deserializer configuration, or schema registry configuration. -- `mode = "full"` generates Spring Boot-oriented client artifacts. This includes producer classes, listener classes, handler interfaces, an auto-configuration class, and the Spring Boot auto-configuration import resource. Generated producers and listeners use topic property keys, for example `kafka.topics.customerUpdated`, instead of hard-coding topic names directly in the generated source. -- `mode = "simple"` generates lightweight producer and consumer source artifacts without Spring Boot auto-configuration. The application owns how those generated types are instantiated and connected to Spring Kafka infrastructure. - -When `mode` is omitted, the generator uses `simple`. - -The generated output depends on the channel direction from the AsyncAPI operations. Producer-oriented channels generate producer artifacts. Consumer-oriented channels generate consumer, listener, or handler artifacts depending on the selected mode. When the channel direction is not declared, the generator treats the channel as both producer and consumer. +The generated output depends on the channel direction from the AsyncAPI operations. Producer-oriented channels generate producer artifacts. Consumer-oriented channels generate consumer artifacts. When the channel direction is not declared, the generator treats the channel as both producer and consumer. The Spring Kafka client surface is still being redesigned for the next major version. The generated artifacts should currently be treated as a source-generation contract, not as final application architecture guidance. diff --git a/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt b/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt index 9cce9227..79b3d437 100644 --- a/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt +++ b/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt @@ -16,7 +16,6 @@ import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorConf import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorConfigurationRequest import dev.banking.asyncapi.generator.core.generator.configuration.JavaModelType import dev.banking.asyncapi.generator.core.generator.model.GeneratorName -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import dev.banking.asyncapi.generator.core.parser.AsyncApiParser import dev.banking.asyncapi.generator.core.registry.AsyncApiRegistry import dev.banking.asyncapi.generator.core.validator.AsyncApiValidator @@ -109,19 +108,6 @@ class AsyncApiGeneratorCli : CliktCommand(name = "asyncapi-generator") { help = "Package containing model types used by generated Spring Kafka clients", ) - private val clientsSpringKafkaMode by option( - "--clients-spring-kafka-mode", - help = "Spring Kafka generation mode (default: simple)", - ).choice( - SpringKafkaClientType.FULL.configurationValue to SpringKafkaClientType.FULL, - SpringKafkaClientType.SIMPLE.configurationValue to SpringKafkaClientType.SIMPLE, - ) - - private val clientsSpringKafkaTopicPropertyPrefix by option( - "--clients-spring-kafka-topic-property-prefix", - help = "Spring Kafka topic property prefix (default: kafka.topics)", - ) - private val clientsQuarkusKafka by option( "--clients-quarkus-kafka", help = "Enable Quarkus Kafka client generation", @@ -225,8 +211,6 @@ class AsyncApiGeneratorCli : CliktCommand(name = "asyncapi-generator") { enabled = true.takeIf { clientsSpringKafka }, packageName = clientsSpringKafkaPackage, modelPackageName = clientsSpringKafkaModelPackage, - mode = clientsSpringKafkaMode?.configurationValue, - topicPropertyPrefix = clientsSpringKafkaTopicPropertyPrefix, ), quarkusKafka = GeneratorConfigurationRequest.quarkusKafka( diff --git a/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt b/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt index ff226124..17c9c599 100644 --- a/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt +++ b/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt @@ -28,7 +28,6 @@ class AsyncApiGeneratorCliTest { "--models-package", "com.example.cli.model", "--clients-spring-kafka-package", "com.example.cli.client", "--generator", "kotlin", - "--clients-spring-kafka-mode", "full", ) ) val packageDir = codegenDir.resolve("src/main/kotlin/com/example/cli/client") @@ -263,24 +262,6 @@ class AsyncApiGeneratorCliTest { ) } - @Test - fun `should fail if spring kafka mode is invalid`(@TempDir tempDir: Path) { - val inputFile = File("src/test/resources/asyncapi_kafka_complex.yaml") - val codegenDir = tempDir.resolve("codegen").toFile() - assertFailsWith { - cli.parse( - arrayOf( - "-i", inputFile.absolutePath, - "--codegen-output", codegenDir.absolutePath, - "--models-package", "com.example.cli.model", - "-g", "kotlin", - "--clients-spring-kafka-package", "com.example.cli.client", - "--clients-spring-kafka-mode", "basic", - ) - ) - } - } - @Test fun `should fail if java model type is invalid`(@TempDir tempDir: Path) { val inputFile = File("src/test/resources/asyncapi_kafka_complex.yaml") diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt index 93f3fb4d..641bc116 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt @@ -1,7 +1,5 @@ package dev.banking.asyncapi.generator.core.generator.configuration -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType - /** * Typed client generation capabilities requested by generator configuration. * @@ -12,8 +10,6 @@ sealed interface ClientGeneration { data class SpringKafka( val packageName: String, val modelPackageName: String, - val clientType: SpringKafkaClientType = SpringKafkaClientType.SIMPLE, - val topicPropertyPrefix: String = "kafka.topics", ) : ClientGeneration data class QuarkusKafka( diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt index f017467f..441c9c8b 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt @@ -64,8 +64,6 @@ object GeneratorConfigurationFactory { configuredModelPackageName = springKafka.modelPackageName, modelsPackageName = request.models?.packageName, ), - clientType = springKafka.clientType, - topicPropertyPrefix = springKafka.topicPropertyPrefix, ), ) } @@ -120,10 +118,6 @@ object GeneratorConfigurationFactory { ) } - if (request.clients.springKafka?.topicPropertyPrefix?.isBlank() == true) { - throw IllegalArgumentException("clients.springKafka.topicPropertyPrefix cannot be empty") - } - validatePackageName( path = "models.packageName", value = request.models?.packageName, diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt index dc43f8e9..735756dd 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt @@ -1,7 +1,6 @@ package dev.banking.asyncapi.generator.core.generator.configuration import dev.banking.asyncapi.generator.core.generator.model.GeneratorName -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import java.io.File /** @@ -55,8 +54,6 @@ data class GeneratorConfigurationRequest( data class SpringKafka( val packageName: String? = null, val modelPackageName: String? = null, - val clientType: SpringKafkaClientType = SpringKafkaClientType.SIMPLE, - val topicPropertyPrefix: String = DEFAULT_KAFKA_TOPICS_PROPERTY_PREFIX, ) data class QuarkusKafka( @@ -65,8 +62,6 @@ data class GeneratorConfigurationRequest( ) companion object { - const val DEFAULT_KAFKA_TOPICS_PROPERTY_PREFIX = "kafka.topics" - fun models( enabled: Boolean? = null, packageName: String? = null, @@ -125,25 +120,15 @@ data class GeneratorConfigurationRequest( enabled: Boolean? = null, packageName: String? = null, modelPackageName: String? = null, - mode: String? = null, - topicPropertyPrefix: String? = null, ): SpringKafka? = when { enabled == false -> null enabled == true || packageName != null || - modelPackageName != null || - mode != null || - topicPropertyPrefix != null -> + modelPackageName != null -> SpringKafka( packageName = packageName, modelPackageName = modelPackageName, - clientType = - SpringKafkaClientType.fromConfigurationValue( - value = mode, - path = "clients.springKafka.mode", - ), - topicPropertyPrefix = topicPropertyPrefix ?: DEFAULT_KAFKA_TOPICS_PROPERTY_PREFIX, ) else -> null } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaKafkaGeneratorModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaKafkaGeneratorModelFactory.kt deleted file mode 100644 index b5427f72..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaKafkaGeneratorModelFactory.kt +++ /dev/null @@ -1,158 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.factory - -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessage -import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaPayload -import dev.banking.asyncapi.generator.core.generator.kafka.spring.NativeKafkaPayloadResolver -import dev.banking.asyncapi.generator.core.generator.util.DocumentationUtils -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryType -import dev.banking.asyncapi.generator.core.model.schemas.Schema -import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface - -class JavaKafkaGeneratorModelFactory( - private val packageName: String, - private val modelPackage: String, - private val topicPropertyPrefix: String, - private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), -) { - fun create(channel: AnalyzedChannel): List { - val items = mutableListOf() - val baseName = MapperUtil.toPascalCase(channel.channelName) - val handlerPackage = "$packageName.handler" - val listenerPackage = "$packageName.listener" - val producerPackage = "$packageName.producer" - val payloads = channel.payloads() - - val baseImports = - payloads.mapNotNull { payload -> payload.importName } - val imports = - (baseImports + "org.apache.kafka.clients.consumer.ConsumerRecord") - .distinct() - .sorted() - - if (channel.isConsumer) { - val topicPropertyKey = topicPropertyKey(channel.channelName) - val topicPrefix = "Topic$baseName" - payloads.forEach { payload -> - val methodName = "on${payload.messageName}" - val handlerName = "${topicPrefix}Handler${payload.messageName}" - items.add( - GeneratorItem.KafkaHandlerInterface( - name = handlerName, - packageName = handlerPackage, - description = DocumentationUtils.toJavaDocLines("Handler for messages on topic '${channel.topic}'"), - methods = - listOf( - GeneratorItem.HandlerMethod( - methodName = methodName, - payloadType = payload.payloadType, - ), - ), - imports = imports, - ), - ) - val listenerName = "${topicPrefix}Listener${payload.messageName}" - val listenerImports = (imports + "$handlerPackage.$handlerName").distinct().sorted() - items.add( - GeneratorItem.KafkaListenerClass( - name = listenerName, - packageName = listenerPackage, - description = DocumentationUtils.toJavaDocLines("Spring Kafka Listener for topic '${channel.topic}'"), - topic = channel.topic, - groupId = "\${spring.kafka.consumer.group-id}", - handlerInterface = handlerName, - payloadType = payload.payloadType, - methodName = methodName, - imports = listenerImports, - topicPropertyKey = topicPropertyKey, - ), - ) - } - } - - if (channel.isProducer) { - val topicPropertyKey = topicPropertyKey(channel.channelName) - val topicPrefix = "Topic$baseName" - payloads.forEach { payload -> - val sendMethod = - GeneratorItem.SendMethod( - methodName = "send${payload.messageName}", - payloadType = payload.payloadType, - ) - val producerName = "${topicPrefix}Producer${payload.messageName}" - items.add( - GeneratorItem.KafkaProducerClass( - name = producerName, - packageName = producerPackage, - description = DocumentationUtils.toJavaDocLines("Producer for topic '${channel.topic}'"), - topic = channel.topic, - sendMethods = listOf(sendMethod), - kafkaValueType = payload.payloadType, - imports = imports, - topicPropertyKey = topicPropertyKey, - ), - ) - } - } - return items - } - - private fun topicPropertyKey(channelName: String): String = "$topicPropertyPrefix.$channelName" - - private fun AnalyzedChannel.payloads(): List = - messages.map(::payload) + multiFormatMessages.mapNotNull(nativeKafkaPayloadResolver::resolve) - - private fun payload(msg: AnalyzedMessage): KafkaPayload { - val type = resolvePayloadType(msg) - return KafkaPayload( - messageName = msg.messageName, - payloadType = type, - importName = - if (isPrimitive(type)) { - null - } else { - "$modelPackage.$type" - }, - ) - } - - private fun resolvePayloadType(msg: AnalyzedMessage): String = - if (isOpenPayloadSchema(msg.schema)) { - "Object" - } else { - when (msg.schema.type.getPrimaryType()) { - "string" -> "String" - "integer" -> "Integer" - "number" -> "java.math.BigDecimal" - "boolean" -> "Boolean" - else -> msg.payloadTypeName - } - } - - private fun isOpenPayloadSchema(schema: Schema): Boolean { - if (schema.type == null) { - return schema.properties.isNullOrEmpty() && - schema.additionalProperties == null && - schema.enum.isNullOrEmpty() && - schema.oneOf.isNullOrEmpty() && - schema.anyOf.isNullOrEmpty() && - schema.allOf.isNullOrEmpty() - } - if (schema.type.getPrimaryType() != "object") return false - if (!schema.properties.isNullOrEmpty()) return false - return when (val additional = schema.additionalProperties) { - null -> true - is SchemaInterface.BooleanSchema -> additional.value - is SchemaInterface.SchemaInline -> - additional.schema.type == null && - additional.schema.properties.isNullOrEmpty() && - additional.schema.additionalProperties == null - else -> false - } - } - - private fun isPrimitive(type: String): Boolean = - type in setOf("String", "Integer", "Long", "Boolean", "Double", "java.math.BigDecimal", "Object") -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaSimpleModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt similarity index 99% rename from asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaSimpleModelFactory.kt rename to asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index 9f6bafd2..612b55b2 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaSimpleModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -11,7 +11,7 @@ import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryT import dev.banking.asyncapi.generator.core.model.schemas.Schema import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface -class JavaSpringKafkaSimpleModelFactory( +class JavaSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaAutoConfigurationGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaAutoConfigurationGenerator.kt deleted file mode 100644 index 997a70e2..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaAutoConfigurationGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationModel -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class JavaSpringKafkaAutoConfigurationGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("java") - - fun generate(model: AutoConfigurationModel) { - val template = mustacheFactory.compile("spring-kafka-autoconfiguration.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.className}.java") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleConsumerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaConsumerGenerator.kt similarity index 85% rename from asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleConsumerGenerator.kt rename to asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaConsumerGenerator.kt index fd49d66b..99daab45 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleConsumerGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaConsumerGenerator.kt @@ -6,13 +6,13 @@ import dev.banking.asyncapi.generator.core.generator.util.FileUtil import java.io.File import java.io.StringWriter -class JavaSpringKafkaSimpleConsumerGenerator( +class JavaSpringKafkaConsumerGenerator( private val outputDir: File, ) { private val mustacheFactory = DefaultMustacheFactory("java") fun generate(model: GeneratorItem.KafkaHandlerInterface) { - val template = mustacheFactory.compile("spring-kafka-simple-consumer.mustache") + val template = mustacheFactory.compile("spring-kafka-consumer.mustache") val writer = StringWriter() template.execute(writer, model).flush() diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt index 7380fad2..b536d096 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt @@ -1,53 +1,27 @@ package dev.banking.asyncapi.generator.core.generator.java.kafka.spring import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.java.factory.JavaKafkaGeneratorModelFactory +import dev.banking.asyncapi.generator.core.generator.java.factory.JavaSpringKafkaModelFactory import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationModel -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationResourceGenerator import java.io.File class JavaSpringKafkaGenerator( outputDir: File, - private val clientPackage: String, + clientPackage: String, modelPackage: String, - topicPropertyPrefix: String, - resourceOutputDir: File, ) { - private val modelFactory = - JavaKafkaGeneratorModelFactory( - this.clientPackage, - modelPackage, - topicPropertyPrefix, - ) - - private val handlerGenerator = JavaSpringKafkaHandlerGenerator(outputDir) - private val listenerGenerator = JavaSpringKafkaListenerGenerator(outputDir) + private val modelFactory = JavaSpringKafkaModelFactory(clientPackage, modelPackage) private val producerGenerator = JavaSpringKafkaProducerGenerator(outputDir) - private val autoConfigGenerator = JavaSpringKafkaAutoConfigurationGenerator(outputDir) - private val autoConfigResourceGenerator = AutoConfigurationResourceGenerator(resourceOutputDir) + private val consumerGenerator = JavaSpringKafkaConsumerGenerator(outputDir) fun generate(channels: List) { - val autoConfigPackage = "${this.clientPackage}.config" - val autoConfigClass = "AsyncApiKafkaAutoConfiguration" - autoConfigGenerator.generate( - AutoConfigurationModel( - packageName = autoConfigPackage, - className = autoConfigClass, - clientPackage = this.clientPackage, - ), - ) - autoConfigResourceGenerator.generate("$autoConfigPackage.$autoConfigClass") - channels.forEach { channel -> val items = modelFactory.create(channel) items.forEach { item -> when (item) { - is GeneratorItem.KafkaHandlerInterface -> handlerGenerator.generate(item) - is GeneratorItem.KafkaListenerClass -> listenerGenerator.generate(item) is GeneratorItem.KafkaProducerClass -> producerGenerator.generate(item) - else -> { // Ignore - } + is GeneratorItem.KafkaHandlerInterface -> consumerGenerator.generate(item) + else -> { /* Ignore */ } } } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaHandlerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaHandlerGenerator.kt deleted file mode 100644 index 8595de36..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaHandlerGenerator.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class JavaSpringKafkaHandlerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("java") - - fun generate(model: GeneratorItem.KafkaHandlerInterface) { - val template = mustacheFactory.compile("spring-kafka-handler.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val file = File(packageDir, "${model.name}.java") - file.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaListenerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaListenerGenerator.kt deleted file mode 100644 index 4dbb7b52..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaListenerGenerator.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class JavaSpringKafkaListenerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("java") - - fun generate(model: GeneratorItem.KafkaListenerClass) { - val template = mustacheFactory.compile("spring-kafka-listener.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val file = File(packageDir, "${model.name}.java") - file.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaProducerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaProducerGenerator.kt index 7768802d..e67cf9a5 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaProducerGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaProducerGenerator.kt @@ -17,7 +17,8 @@ class JavaSpringKafkaProducerGenerator( template.execute(writer, model).flush() val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val file = File(packageDir, "${model.name}.java") - file.writeText(writer.toString()) + val outputFile = File(packageDir, "${model.name}.java") + outputFile.parentFile.mkdirs() + outputFile.writeText(writer.toString()) } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleGenerator.kt deleted file mode 100644 index dfd041c6..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleGenerator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka.spring - -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.java.factory.JavaSpringKafkaSimpleModelFactory -import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import java.io.File - -class JavaSpringKafkaSimpleGenerator( - outputDir: File, - clientPackage: String, - modelPackage: String, -) { - private val modelFactory = JavaSpringKafkaSimpleModelFactory(clientPackage, modelPackage) - private val producerGenerator = JavaSpringKafkaSimpleProducerGenerator(outputDir) - private val consumerGenerator = JavaSpringKafkaSimpleConsumerGenerator(outputDir) - - fun generate(channels: List) { - channels.forEach { channel -> - val items = modelFactory.create(channel) - items.forEach { item -> - when (item) { - is GeneratorItem.KafkaProducerClass -> producerGenerator.generate(item) - is GeneratorItem.KafkaHandlerInterface -> consumerGenerator.generate(item) - else -> { /* Ignore */ } - } - } - } - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleProducerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleProducerGenerator.kt deleted file mode 100644 index 59e88594..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaSimpleProducerGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class JavaSpringKafkaSimpleProducerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("java") - - fun generate(model: GeneratorItem.KafkaProducerClass) { - val template = mustacheFactory.compile("spring-kafka-simple-producer.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.name}.java") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationModel.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationModel.kt deleted file mode 100644 index c23ba864..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationModel.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kafka.spring - -data class AutoConfigurationModel( - val packageName: String, - val className: String, - val clientPackage: String, -) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationResourceGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationResourceGenerator.kt deleted file mode 100644 index 08972e7d..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/AutoConfigurationResourceGenerator.kt +++ /dev/null @@ -1,15 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kafka.spring - -import java.io.File - -class AutoConfigurationResourceGenerator( - private val resourceOutputDir: File, -) { - fun generate(autoConfigClassName: String) { - val file = File(resourceOutputDir, "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") - file.parentFile.mkdirs() - val existing = if (file.exists()) file.readText().lines().filter { it.isNotBlank() } else emptyList() - val updated = (existing + autoConfigClassName).distinct().joinToString("\n") - file.writeText(updated + "\n") - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/NativeKafkaPayloadResolver.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/NativeKafkaPayloadResolver.kt index e78bf27a..b6a823cc 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/NativeKafkaPayloadResolver.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/NativeKafkaPayloadResolver.kt @@ -9,9 +9,7 @@ import dev.banking.asyncapi.generator.core.generator.protobuf.NativeProtobufPayl * * Expected behavior is covered by: * - `GenerateKotlinSpringKafkaTest` - * - `GenerateKotlinSpringKafkaSimpleTest` - * - `GenerateJavaSpringKafkaClientTest` - * - `GenerateJavaSpringKafkaSimpleTest` + * - `GenerateJavaSpringKafkaTest` */ class NativeKafkaPayloadResolver( private val nativeAvroPayloadTypeResolver: NativeAvroPayloadTypeResolver = NativeAvroPayloadTypeResolver(), diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt index f4e36603..c87c732f 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt @@ -2,20 +2,14 @@ package dev.banking.asyncapi.generator.core.generator.kafka.spring import dev.banking.asyncapi.generator.core.generator.input.GenerationInput import dev.banking.asyncapi.generator.core.generator.java.kafka.spring.JavaSpringKafkaGenerator -import dev.banking.asyncapi.generator.core.generator.java.kafka.spring.JavaSpringKafkaSimpleGenerator import dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring.KotlinSpringKafkaGenerator -import dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring.KotlinSpringKafkaSimpleGenerator import dev.banking.asyncapi.generator.core.generator.model.GeneratorName.JAVA import dev.banking.asyncapi.generator.core.generator.model.GeneratorName.KOTLIN import dev.banking.asyncapi.generator.core.generator.plan.GenerationTask -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import java.io.File /** - * Dispatches planned Spring Kafka client generation to the current implementations. - * - * This is the boundary for the existing Spring Kafka client generators while - * the long-term client surface is still being designed. + * Dispatches planned Spring Kafka client generation to the supported contract generator. * * Expected behavior is covered by: * - `SpringKafkaClientGenerationTest` @@ -25,11 +19,12 @@ class SpringKafkaClientGeneration { task: GenerationTask.SpringKafkaClient, generationInput: GenerationInput, sourceOutputDirectory: File, + @Suppress("UNUSED_PARAMETER") resourceOutputDirectory: File, ) { when (task.language) { - KOTLIN -> generateKotlinClient(task, generationInput, sourceOutputDirectory, resourceOutputDirectory) - JAVA -> generateJavaClient(task, generationInput, sourceOutputDirectory, resourceOutputDirectory) + KOTLIN -> generateKotlinClient(task, generationInput, sourceOutputDirectory) + JAVA -> generateJavaClient(task, generationInput, sourceOutputDirectory) } } @@ -37,53 +32,27 @@ class SpringKafkaClientGeneration { task: GenerationTask.SpringKafkaClient, generationInput: GenerationInput, sourceOutputDirectory: File, - resourceOutputDirectory: File, ) { - if (task.clientType == SpringKafkaClientType.SIMPLE) { - val kafkaGenerator = - KotlinSpringKafkaSimpleGenerator( - outputDir = sourceOutputDirectory, - clientPackage = task.clientPackage, - modelPackage = task.modelPackage, - ) - kafkaGenerator.generate(generationInput.channels) - } else { - val kafkaGenerator = - KotlinSpringKafkaGenerator( - outputDir = sourceOutputDirectory, - clientPackage = task.clientPackage, - modelPackage = task.modelPackage, - topicPropertyPrefix = task.topicPropertyPrefix, - resourceOutputDir = resourceOutputDirectory, - ) - kafkaGenerator.generate(generationInput.channels) - } + val kafkaGenerator = + KotlinSpringKafkaGenerator( + outputDir = sourceOutputDirectory, + clientPackage = task.clientPackage, + modelPackage = task.modelPackage, + ) + kafkaGenerator.generate(generationInput.channels) } private fun generateJavaClient( task: GenerationTask.SpringKafkaClient, generationInput: GenerationInput, sourceOutputDirectory: File, - resourceOutputDirectory: File, ) { - if (task.clientType == SpringKafkaClientType.SIMPLE) { - val kafkaGenerator = - JavaSpringKafkaSimpleGenerator( - outputDir = sourceOutputDirectory, - clientPackage = task.clientPackage, - modelPackage = task.modelPackage, - ) - kafkaGenerator.generate(generationInput.channels) - } else { - val kafkaGenerator = - JavaSpringKafkaGenerator( - outputDir = sourceOutputDirectory, - clientPackage = task.clientPackage, - modelPackage = task.modelPackage, - topicPropertyPrefix = task.topicPropertyPrefix, - resourceOutputDir = resourceOutputDirectory, - ) - kafkaGenerator.generate(generationInput.channels) - } + val kafkaGenerator = + JavaSpringKafkaGenerator( + outputDir = sourceOutputDirectory, + clientPackage = task.clientPackage, + modelPackage = task.modelPackage, + ) + kafkaGenerator.generate(generationInput.channels) } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinKafkaGeneratorModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinKafkaGeneratorModelFactory.kt deleted file mode 100644 index 443f3b4c..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinKafkaGeneratorModelFactory.kt +++ /dev/null @@ -1,131 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.factory - -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessage -import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaPayload -import dev.banking.asyncapi.generator.core.generator.kafka.spring.NativeKafkaPayloadResolver -import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.DocumentationUtils.toKDocLines -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryType - -class KotlinKafkaGeneratorModelFactory( - private val packageName: String, - private val modelPackage: String, - private val topicPropertyPrefix: String, - private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), -) { - fun create(channel: AnalyzedChannel): List { - val items = mutableListOf() - val baseName = MapperUtil.toPascalCase(channel.channelName) - val handlerPackage = "$packageName.handler" - val listenerPackage = "$packageName.listener" - val producerPackage = "$packageName.producer" - val payloads = channel.payloads() - - val baseImports = - payloads.mapNotNull { payload -> payload.importName } - val imports = - (baseImports + "org.apache.kafka.clients.consumer.ConsumerRecord") - .distinct() - .sorted() - - if (channel.isConsumer) { - val topicPropertyKey = topicPropertyKey(channel.channelName) - val topicPrefix = "Topic$baseName" - payloads.forEach { payload -> - val methodName = "on${payload.messageName}" - val handlerName = "${topicPrefix}Handler${payload.messageName}" - items.add( - GeneratorItem.KafkaHandlerInterface( - name = handlerName, - packageName = handlerPackage, - description = toKDocLines("Handler for messages on topic '${channel.topic}'"), - methods = - listOf( - GeneratorItem.HandlerMethod( - methodName = methodName, - payloadType = payload.payloadType, - keyType = "String?", - ), - ), - imports = imports, - ), - ) - val listenerName = "${topicPrefix}Listener${payload.messageName}" - val listenerImports = (imports + "$handlerPackage.$handlerName").distinct().sorted() - items.add( - GeneratorItem.KafkaListenerClass( - name = listenerName, - packageName = listenerPackage, - description = toKDocLines("Spring Kafka Listener for topic '${channel.topic}'"), - topic = channel.topic, - groupId = "\\\${spring.kafka.consumer.group-id}", - handlerInterface = handlerName, - payloadType = payload.payloadType, - methodName = methodName, - imports = listenerImports, - topicPropertyKey = topicPropertyKey, - ), - ) - } - } - - if (channel.isProducer) { - val topicPropertyKey = topicPropertyKey(channel.channelName) - val topicPrefix = "Topic$baseName" - payloads.forEach { payload -> - val sendMethod = - GeneratorItem.SendMethod( - methodName = "send${payload.messageName}", - payloadType = payload.payloadType, - keyType = "String", - ) - val producerName = "${topicPrefix}Producer${payload.messageName}" - items.add( - GeneratorItem.KafkaProducerClass( - name = producerName, - packageName = producerPackage, - description = toKDocLines("Producer for topic '${channel.topic}'"), - topic = channel.topic, - sendMethods = listOf(sendMethod), - kafkaValueType = payload.payloadType, - imports = imports, - topicPropertyKey = topicPropertyKey, - ), - ) - } - } - return items - } - - private fun topicPropertyKey(channelName: String): String = "$topicPropertyPrefix.$channelName" - - private fun AnalyzedChannel.payloads(): List = - messages.map(::payload) + multiFormatMessages.mapNotNull(nativeKafkaPayloadResolver::resolve) - - private fun payload(msg: AnalyzedMessage): KafkaPayload { - val type = resolvePayloadType(msg) - return KafkaPayload( - messageName = msg.messageName, - payloadType = type, - importName = - if (isPrimitive(type)) { - null - } else { - "$modelPackage.$type" - }, - ) - } - - private fun resolvePayloadType(msg: AnalyzedMessage): String = - when (msg.schema.type.getPrimaryType()) { - "string" -> "String" - "integer" -> "Int" // Simplified, could check format for Long - "number" -> "java.math.BigDecimal" - "boolean" -> "Boolean" - else -> msg.payloadTypeName // Object types use the Class Name - } - - private fun isPrimitive(type: String): Boolean = type in setOf("String", "Int", "Long", "Boolean", "java.math.BigDecimal") -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaSimpleModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt similarity index 99% rename from asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaSimpleModelFactory.kt rename to asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index 24388698..5a5028b5 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaSimpleModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -9,7 +9,7 @@ import dev.banking.asyncapi.generator.core.generator.util.DocumentationUtils.toK import dev.banking.asyncapi.generator.core.generator.util.MapperUtil import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryType -class KotlinSpringKafkaSimpleModelFactory( +class KotlinSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaAutoConfigurationGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaAutoConfigurationGenerator.kt deleted file mode 100644 index cd7b3233..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaAutoConfigurationGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationModel -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class KotlinSpringKafkaAutoConfigurationGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("kotlin") - - fun generate(model: AutoConfigurationModel) { - val template = mustacheFactory.compile("spring-kafka-autoconfiguration.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.className}.kt") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaHandlerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaConsumerGenerator.kt similarity index 86% rename from asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaHandlerGenerator.kt rename to asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaConsumerGenerator.kt index 10fc0398..3df32f07 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaHandlerGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaConsumerGenerator.kt @@ -6,13 +6,13 @@ import dev.banking.asyncapi.generator.core.generator.util.FileUtil import java.io.File import java.io.StringWriter -class KotlinSpringKafkaHandlerGenerator( +class KotlinSpringKafkaConsumerGenerator( private val outputDir: File, ) { private val mustacheFactory = DefaultMustacheFactory("kotlin") fun generate(model: GeneratorItem.KafkaHandlerInterface) { - val template = mustacheFactory.compile("spring-kafka-handler.mustache") + val template = mustacheFactory.compile("spring-kafka-consumer.mustache") val writer = StringWriter() template.execute(writer, model).flush() diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt index 78c08269..b21d44fa 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt @@ -1,51 +1,27 @@ package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationModel -import dev.banking.asyncapi.generator.core.generator.kafka.spring.AutoConfigurationResourceGenerator -import dev.banking.asyncapi.generator.core.generator.kotlin.factory.KotlinKafkaGeneratorModelFactory +import dev.banking.asyncapi.generator.core.generator.kotlin.factory.KotlinSpringKafkaModelFactory import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem import java.io.File class KotlinSpringKafkaGenerator( outputDir: File, - private val clientPackage: String, + clientPackage: String, modelPackage: String, - topicPropertyPrefix: String, - resourceOutputDir: File, ) { - private val modelFactory = - KotlinKafkaGeneratorModelFactory( - this.clientPackage, - modelPackage, - topicPropertyPrefix, - ) - private val handlerGenerator = KotlinSpringKafkaHandlerGenerator(outputDir) - private val listenerGenerator = KotlinSpringKafkaListenerGenerator(outputDir) + private val modelFactory = KotlinSpringKafkaModelFactory(clientPackage, modelPackage) private val producerGenerator = KotlinSpringKafkaProducerGenerator(outputDir) - private val autoConfigGenerator = KotlinSpringKafkaAutoConfigurationGenerator(outputDir) - private val autoConfigResourceGenerator = AutoConfigurationResourceGenerator(resourceOutputDir) + private val consumerGenerator = KotlinSpringKafkaConsumerGenerator(outputDir) fun generate(channels: List) { - val autoConfigPackage = "${this.clientPackage}.config" - val autoConfigClass = "AsyncApiKafkaAutoConfiguration" - autoConfigGenerator.generate( - AutoConfigurationModel( - packageName = autoConfigPackage, - className = autoConfigClass, - clientPackage = this.clientPackage, - ), - ) - autoConfigResourceGenerator.generate("$autoConfigPackage.$autoConfigClass") - channels.forEach { channel -> val items = modelFactory.create(channel) items.forEach { item -> when (item) { - is GeneratorItem.KafkaHandlerInterface -> handlerGenerator.generate(item) - is GeneratorItem.KafkaListenerClass -> listenerGenerator.generate(item) is GeneratorItem.KafkaProducerClass -> producerGenerator.generate(item) - else -> { /* Ignore other types if mixed */ } + is GeneratorItem.KafkaHandlerInterface -> consumerGenerator.generate(item) + else -> { /* Ignore */ } } } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaListenerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaListenerGenerator.kt deleted file mode 100644 index 6b6971d5..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaListenerGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class KotlinSpringKafkaListenerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("kotlin") - - fun generate(model: GeneratorItem.KafkaListenerClass) { - val template = mustacheFactory.compile("spring-kafka-listener.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.name}.kt") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleConsumerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleConsumerGenerator.kt deleted file mode 100644 index 9cf69ce5..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleConsumerGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class KotlinSpringKafkaSimpleConsumerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("kotlin") - - fun generate(model: GeneratorItem.KafkaHandlerInterface) { - val template = mustacheFactory.compile("spring-kafka-simple-consumer.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.name}.kt") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleGenerator.kt deleted file mode 100644 index 4c188681..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleGenerator.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring - -import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel -import dev.banking.asyncapi.generator.core.generator.kotlin.factory.KotlinSpringKafkaSimpleModelFactory -import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem -import java.io.File - -class KotlinSpringKafkaSimpleGenerator( - outputDir: File, - clientPackage: String, - modelPackage: String, -) { - private val modelFactory = KotlinSpringKafkaSimpleModelFactory(clientPackage, modelPackage) - private val producerGenerator = KotlinSpringKafkaSimpleProducerGenerator(outputDir) - private val consumerGenerator = KotlinSpringKafkaSimpleConsumerGenerator(outputDir) - - fun generate(channels: List) { - channels.forEach { channel -> - val items = modelFactory.create(channel) - items.forEach { item -> - when (item) { - is GeneratorItem.KafkaProducerClass -> producerGenerator.generate(item) - is GeneratorItem.KafkaHandlerInterface -> consumerGenerator.generate(item) - else -> { /* Ignore */ } - } - } - } - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleProducerGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleProducerGenerator.kt deleted file mode 100644 index 43af4a2d..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaSimpleProducerGenerator.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka.spring - -import com.github.mustachejava.DefaultMustacheFactory -import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem -import dev.banking.asyncapi.generator.core.generator.util.FileUtil -import java.io.File -import java.io.StringWriter - -class KotlinSpringKafkaSimpleProducerGenerator( - private val outputDir: File, -) { - private val mustacheFactory = DefaultMustacheFactory("kotlin") - - fun generate(model: GeneratorItem.KafkaProducerClass) { - val template = mustacheFactory.compile("spring-kafka-simple-producer.mustache") - val writer = StringWriter() - template.execute(writer, model).flush() - - val packageDir = FileUtil.packageDirectory(outputDir, model.packageName) - val outputFile = File(packageDir, "${model.name}.kt") - outputFile.parentFile.mkdirs() - outputFile.writeText(writer.toString()) - } -} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt index 9b364e25..222b184e 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt @@ -31,25 +31,17 @@ class GenerationPlanner { configuration.clients.forEach { client -> when (client) { is ClientGeneration.SpringKafka -> { - require(client.topicPropertyPrefix.isNotBlank()) { - "topicPropertyPrefix cannot be empty" - } - - if (client.clientType != SpringKafkaClientType.SIMPLE) { - add( - GenerationTask.HeaderModelArtifacts( - language = configuration.language, - packageName = "${client.packageName}.header", - ), - ) - } + add( + GenerationTask.HeaderModelArtifacts( + language = configuration.language, + packageName = "${client.packageName}.header", + ), + ) add( GenerationTask.SpringKafkaClient( language = configuration.language, - clientType = client.clientType, clientPackage = client.packageName, modelPackage = client.modelPackageName, - topicPropertyPrefix = client.topicPropertyPrefix, ), ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt index 5e529885..a2dfbe16 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt @@ -24,10 +24,8 @@ sealed interface GenerationTask { data class SpringKafkaClient( val language: GeneratorName, - val clientType: SpringKafkaClientType, val clientPackage: String, val modelPackage: String, - val topicPropertyPrefix: String, ) : GenerationTask data class QuarkusKafkaClient( diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientType.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientType.kt deleted file mode 100644 index c63d6363..00000000 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientType.kt +++ /dev/null @@ -1,37 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.plan - -/** - * Spring Kafka client generation mode selected during planning. - * - * The simple client remains the default when a caller enables Spring Kafka generation - * without selecting another mode. - * - * Expected behavior is covered by: - * - `GenerationPlannerTest` - * - `SpringKafkaClientTypeTest` - */ -enum class SpringKafkaClientType( - val configurationValue: String, -) { - FULL("full"), - SIMPLE("simple"), - ; - - companion object { - val supportedConfigurationValues: List = entries.map { it.configurationValue } - - fun fromConfigurationValue( - value: String?, - path: String, - ): SpringKafkaClientType { - if (value == null) { - return SIMPLE - } - - return entries.firstOrNull { it.configurationValue == value } - ?: throw IllegalArgumentException( - "Invalid $path '$value'. Supported values: ${supportedConfigurationValues.joinToString(", ")}", - ) - } - } -} diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-autoconfiguration.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-autoconfiguration.mustache deleted file mode 100644 index 36fd97b0..00000000 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-autoconfiguration.mustache +++ /dev/null @@ -1,9 +0,0 @@ -package {{packageName}}; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.boot.autoconfigure.AutoConfiguration; - -@AutoConfiguration -@ComponentScan(basePackages = "{{clientPackage}}") -public class {{className}} { -} diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-consumer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache similarity index 100% rename from asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-consumer.mustache rename to asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-handler.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-handler.mustache deleted file mode 100644 index dea03bc7..00000000 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-handler.mustache +++ /dev/null @@ -1,16 +0,0 @@ -package {{packageName}}; - -{{#imports}} -import {{{.}}}; -{{/imports}} - -/** -{{#description}} - * {{{.}}} -{{/description}} - */ -public interface {{name}} { -{{#methods}} - void {{methodName}}(ConsumerRecord record); -{{/methods}} -} diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-listener.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-listener.mustache deleted file mode 100644 index 153c45e1..00000000 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-listener.mustache +++ /dev/null @@ -1,36 +0,0 @@ -package {{packageName}}; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -{{#imports}} -import {{.}}; -{{/imports}} - -/** -{{#description}} - * {{.}} -{{/description}} - */ -@Component -@ConditionalOnBean({{handlerInterface}}.class) -@ConditionalOnProperty(name = "{{topicPropertyKey}}") -public class {{name}} { - - private static final Logger log = LoggerFactory.getLogger({{name}}.class); - private final {{handlerInterface}} handler; - - public {{name}}({{handlerInterface}} handler) { - this.handler = handler; - } - - {{=<% %>=}} - @KafkaListener(topics = "${<%topicPropertyKey%>}", groupId = "<%groupId%>") - <%={{ }}=%> public void listen(ConsumerRecord record) { - log.debug("Dispatching {{payloadType}} from topic {{topic}}"); - handler.{{methodName}}(record); - } -} diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache index d2e4fffc..acf76f6f 100644 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache @@ -1,12 +1,5 @@ package {{packageName}}; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; {{#imports}} import {{{.}}}; {{/imports}} @@ -16,24 +9,18 @@ import {{{.}}}; * {{{.}}} {{/description}} */ -@Component -@ConditionalOnProperty(name = "{{topicPropertyKey}}") public class {{name}} { - private static final Logger log = LoggerFactory.getLogger({{name}}.class); private final KafkaTemplate kafkaTemplate; + private final String topic; - {{=<% %>=}} - @Value("${<%topicPropertyKey%>}") - private String topic; - <%={{ }}=%> - public {{name}}(KafkaTemplate kafkaTemplate) { + public {{name}}(KafkaTemplate kafkaTemplate, String topic) { this.kafkaTemplate = kafkaTemplate; + this.topic = topic; } {{#sendMethods}} public void {{methodName}}(String key, {{payloadType}} message) { - log.info("Sending {{payloadType}} to topic={}, key={}", topic, key); kafkaTemplate.send(new ProducerRecord<>(topic, key, message)); } {{/sendMethods}} diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-producer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-producer.mustache deleted file mode 100644 index acf76f6f..00000000 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-simple-producer.mustache +++ /dev/null @@ -1,27 +0,0 @@ -package {{packageName}}; - -{{#imports}} -import {{{.}}}; -{{/imports}} - -/** -{{#description}} - * {{{.}}} -{{/description}} - */ -public class {{name}} { - - private final KafkaTemplate kafkaTemplate; - private final String topic; - - public {{name}}(KafkaTemplate kafkaTemplate, String topic) { - this.kafkaTemplate = kafkaTemplate; - this.topic = topic; - } - - {{#sendMethods}} - public void {{methodName}}(String key, {{payloadType}} message) { - kafkaTemplate.send(new ProducerRecord<>(topic, key, message)); - } - {{/sendMethods}} -} diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-autoconfiguration.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-autoconfiguration.mustache deleted file mode 100644 index 05c56d28..00000000 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-autoconfiguration.mustache +++ /dev/null @@ -1,8 +0,0 @@ -package {{packageName}} - -import org.springframework.context.annotation.ComponentScan -import org.springframework.boot.autoconfigure.AutoConfiguration - -@AutoConfiguration -@ComponentScan(basePackages = ["{{clientPackage}}"]) -class {{className}} diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-consumer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache similarity index 100% rename from asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-consumer.mustache rename to asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-handler.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-handler.mustache deleted file mode 100644 index 53671a16..00000000 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-handler.mustache +++ /dev/null @@ -1,16 +0,0 @@ -package {{packageName}} - -{{#imports}} -import {{{.}}} -{{/imports}} - -/** -{{#description}} - * {{{.}}} -{{/description}} - */ -interface {{name}} { -{{#methods}} - fun {{methodName}}(record: ConsumerRecord) -{{/methods}} -} diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-listener.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-listener.mustache deleted file mode 100644 index 4985595a..00000000 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-listener.mustache +++ /dev/null @@ -1,32 +0,0 @@ -package {{packageName}} - -import org.slf4j.LoggerFactory -import org.springframework.kafka.annotation.KafkaListener -import org.springframework.stereotype.Component -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -{{#imports}} -import {{{.}}} -{{/imports}} - - -/** -{{#description}} - * {{{.}}} -{{/description}} - */ -@Component -@ConditionalOnBean({{handlerInterface}}::class) -@ConditionalOnProperty(name = ["{{topicPropertyKey}}"]) -class {{name}}( - private val handler: {{handlerInterface}} -) { - - private val log = LoggerFactory.getLogger({{name}}::class.java) -{{=<% %>=}} - @KafkaListener(topics = ["\${<%topicPropertyKey%>}"], groupId = "<%groupId%>") -<%={{ }}=%> fun listen(record: ConsumerRecord) { - log.debug("Dispatching {{payloadType}} from topic {{topic}}") - handler.{{methodName}}(record) - } - } diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache index 73bc7eee..8ec1dd77 100644 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache @@ -1,11 +1,5 @@ package {{packageName}} -import org.apache.kafka.clients.producer.ProducerRecord -import org.slf4j.LoggerFactory -import org.springframework.kafka.core.KafkaTemplate -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Component {{#imports}} import {{{.}}} {{/imports}} @@ -15,19 +9,12 @@ import {{{.}}} * {{{.}}} {{/description}} */ -@Component -@ConditionalOnProperty(name = ["{{topicPropertyKey}}"]) class {{name}}( - private val kafkaTemplate: KafkaTemplate + private val kafkaTemplate: KafkaTemplate, + private val topic: String ) { - private val log = LoggerFactory.getLogger({{name}}::class.java) - {{=<% %>=}} - @Value("\${<%topicPropertyKey%>}") - private lateinit var topic: String - <%={{ }}=%> {{#sendMethods}} fun {{methodName}}(key: String, message: {{payloadType}}) { - log.info("Sending {{payloadType}} to topic={}, key={}", topic, key) kafkaTemplate.send(ProducerRecord(topic, key, message)) } {{/sendMethods}} diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-producer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-producer.mustache deleted file mode 100644 index 8ec1dd77..00000000 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-simple-producer.mustache +++ /dev/null @@ -1,21 +0,0 @@ -package {{packageName}} - -{{#imports}} -import {{{.}}} -{{/imports}} - -/** -{{#description}} - * {{{.}}} -{{/description}} - */ -class {{name}}( - private val kafkaTemplate: KafkaTemplate, - private val topic: String -) { - {{#sendMethods}} - fun {{methodName}}(key: String, message: {{payloadType}}) { - kafkaTemplate.send(ProducerRecord(topic, key, message)) - } - {{/sendMethods}} -} diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt index a34035cc..072df530 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt @@ -8,7 +8,6 @@ import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorOutp import dev.banking.asyncapi.generator.core.generator.configuration.JavaModelType import dev.banking.asyncapi.generator.core.generator.configuration.ModelGeneration import dev.banking.asyncapi.generator.core.generator.model.GeneratorName -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import java.io.File abstract class AbstractJavaGeneratorClass { @@ -27,8 +26,6 @@ abstract class AbstractJavaGeneratorClass { generateModels: Boolean = true, generateSpringKafkaClient: Boolean = false, generateQuarkusKafkaClient: Boolean = false, - kafkaTopicsPropertyPrefix: String = "kafka.topics", - springKafkaClientType: SpringKafkaClientType = SpringKafkaClientType.FULL, modelAnnotation: String? = null, javaModelType: JavaModelType = JavaModelType.CLASS, ): String { @@ -59,8 +56,6 @@ abstract class AbstractJavaGeneratorClass { ClientGeneration.SpringKafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, - clientType = springKafkaClientType, - topicPropertyPrefix = kafkaTopicsPropertyPrefix, ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt index 4856f7e6..24b6248e 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt @@ -7,7 +7,6 @@ import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorConf import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorOutputConfiguration import dev.banking.asyncapi.generator.core.generator.configuration.ModelGeneration import dev.banking.asyncapi.generator.core.generator.model.GeneratorName -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import java.io.File abstract class AbstractKotlinGeneratorClass { @@ -26,8 +25,6 @@ abstract class AbstractKotlinGeneratorClass { generateModels: Boolean = true, generateSpringKafkaClient: Boolean = false, generateQuarkusKafkaClient: Boolean = false, - kafkaTopicsPropertyPrefix: String = "kafka.topics", - springKafkaClientType: SpringKafkaClientType = SpringKafkaClientType.FULL, modelAnnotation: String? = null, ): String { val bundled = bundlerFixtures.bundledDocument(yaml) @@ -56,8 +53,6 @@ abstract class AbstractKotlinGeneratorClass { ClientGeneration.SpringKafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, - clientType = springKafkaClientType, - topicPropertyPrefix = kafkaTopicsPropertyPrefix, ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt index 0ae17d81..19d5e4e9 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt @@ -9,7 +9,6 @@ import dev.banking.asyncapi.generator.core.generator.configuration.GeneratorOutp import dev.banking.asyncapi.generator.core.generator.configuration.ModelGeneration import dev.banking.asyncapi.generator.core.generator.configuration.SchemaGeneration import dev.banking.asyncapi.generator.core.generator.model.GeneratorName -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import dev.banking.asyncapi.generator.core.model.exceptions.AsyncApiGeneratorException import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir @@ -249,7 +248,6 @@ class AsyncApiGeneratorOutputContractTest { ClientGeneration.SpringKafka( packageName = "com.example.kafka", modelPackageName = "com.example.model", - clientType = SpringKafkaClientType.SIMPLE, ), ), ), diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt index e40449f5..bed684d8 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt @@ -2,7 +2,6 @@ package dev.banking.asyncapi.generator.core.generator.configuration import dev.banking.asyncapi.generator.core.generator.model.GeneratorName import dev.banking.asyncapi.generator.core.generator.model.GeneratorName.JAVA -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File @@ -78,7 +77,7 @@ class GeneratorConfigurationFactoryTest { } @Test - fun `create enables Spring Kafka client generation when client type is configured`() { + fun `create enables Spring Kafka client generation when client package is configured`() { val configuration = GeneratorConfigurationFactory.create( request( @@ -88,8 +87,6 @@ class GeneratorConfigurationFactoryTest { springKafka = GeneratorConfigurationRequest.SpringKafka( packageName = "com.example.client", - clientType = SpringKafkaClientType.SIMPLE, - topicPropertyPrefix = "custom.topics", ), ), ), @@ -100,8 +97,6 @@ class GeneratorConfigurationFactoryTest { ClientGeneration.SpringKafka( packageName = "com.example.client", modelPackageName = "com.example.model", - clientType = SpringKafkaClientType.SIMPLE, - topicPropertyPrefix = "custom.topics", ), ), configuration.clients, @@ -129,7 +124,6 @@ class GeneratorConfigurationFactoryTest { ClientGeneration.SpringKafka( packageName = "com.example.client", modelPackageName = "com.example.external.model", - clientType = SpringKafkaClientType.SIMPLE, ), ), configuration.clients, @@ -329,27 +323,6 @@ class GeneratorConfigurationFactoryTest { ) } - @Test - fun `create rejects blank Kafka topics property prefix`() { - val exception = - assertFailsWith { - GeneratorConfigurationFactory.create( - request( - clients = - GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( - packageName = "com.example.client", - topicPropertyPrefix = "", - ), - ), - ), - ) - } - - assertEquals("clients.springKafka.topicPropertyPrefix cannot be empty", exception.message) - } - @Test fun `create rejects empty package names`() { assertConfigurationError( diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt index 99a80ed4..3f408853 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt @@ -1,6 +1,4 @@ package dev.banking.asyncapi.generator.core.generator.configuration - -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -91,8 +89,6 @@ class GeneratorConfigurationRequestTest { enabled = false, packageName = "com.example.client", modelPackageName = "com.example.model", - mode = "full", - topicPropertyPrefix = "custom.topics", ), ) @@ -100,25 +96,19 @@ class GeneratorConfigurationRequestTest { GeneratorConfigurationRequest.SpringKafka( packageName = "com.example.client", modelPackageName = "com.example.model", - clientType = SpringKafkaClientType.FULL, - topicPropertyPrefix = "custom.topics", ), GeneratorConfigurationRequest.springKafka( packageName = "com.example.client", modelPackageName = "com.example.model", - mode = "full", - topicPropertyPrefix = "custom.topics", ), ) } @Test - fun `spring kafka request defaults to simple mode and default topic prefix`() { + fun `spring kafka request can be created from package only`() { assertEquals( GeneratorConfigurationRequest.SpringKafka( packageName = "com.example.client", - clientType = SpringKafkaClientType.SIMPLE, - topicPropertyPrefix = GeneratorConfigurationRequest.DEFAULT_KAFKA_TOPICS_PROPERTY_PREFIX, ), GeneratorConfigurationRequest.springKafka(packageName = "com.example.client"), ) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/input/GenerationInputCompatibilityValidatorTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/input/GenerationInputCompatibilityValidatorTest.kt index 67757331..c1b20a0c 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/input/GenerationInputCompatibilityValidatorTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/input/GenerationInputCompatibilityValidatorTest.kt @@ -6,7 +6,6 @@ import dev.banking.asyncapi.generator.core.generator.configuration.JavaModelType import dev.banking.asyncapi.generator.core.generator.model.GeneratorName import dev.banking.asyncapi.generator.core.generator.plan.GenerationPlan import dev.banking.asyncapi.generator.core.generator.plan.GenerationTask -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import dev.banking.asyncapi.generator.core.model.exceptions.AsyncApiGeneratorException import dev.banking.asyncapi.generator.core.model.schemas.MultiFormatSchema import dev.banking.asyncapi.generator.core.model.schemas.Schema @@ -94,10 +93,8 @@ class GenerationInputCompatibilityValidatorTest { listOf( GenerationTask.SpringKafkaClient( language = GeneratorName.KOTLIN, - clientType = SpringKafkaClientType.SIMPLE, clientPackage = "com.example.kafka", modelPackage = "com.example.model", - topicPropertyPrefix = "kafka.topics", ), ), ), @@ -113,10 +110,8 @@ class GenerationInputCompatibilityValidatorTest { listOf( GenerationTask.SpringKafkaClient( language = GeneratorName.KOTLIN, - clientType = SpringKafkaClientType.SIMPLE, clientPackage = "com.example.kafka", modelPackage = "com.example.model", - topicPropertyPrefix = "kafka.topics", ), ), ), @@ -140,10 +135,8 @@ class GenerationInputCompatibilityValidatorTest { listOf( GenerationTask.SpringKafkaClient( language = GeneratorName.KOTLIN, - clientType = SpringKafkaClientType.SIMPLE, clientPackage = "com.example.kafka", modelPackage = "com.example.model", - topicPropertyPrefix = "kafka.topics", ), ), ), diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt index 2aae20f5..e7b514cf 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt @@ -35,8 +35,6 @@ class GenerateJavaPrimitivePayloadTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val producerFile = @@ -44,7 +42,7 @@ class GenerateJavaPrimitivePayloadTest { packageName.replace( '.', '/' - ) + "/producer/TopicSimpleTopicProducerSimpleStringMessage.java" + ) + "/producer/SimpleTopicProducerSimpleStringMessage.java" ) assertTrue(producerFile.exists(), "Producer should be generated") val producerContent = producerFile.readText() @@ -85,14 +83,12 @@ class GenerateJavaPrimitivePayloadTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val producerFileA = - outputDir.resolve(packageName.replace('.', '/') + "/producer/TopicMultiTopicProducerStringMessage.java") + outputDir.resolve(packageName.replace('.', '/') + "/producer/MultiTopicProducerStringMessage.java") val producerFileB = - outputDir.resolve(packageName.replace('.', '/') + "/producer/TopicMultiTopicProducerIntMessage.java") + outputDir.resolve(packageName.replace('.', '/') + "/producer/MultiTopicProducerIntMessage.java") assertTrue(producerFileA.exists(), "StringMessage producer should be generated") assertTrue(producerFileB.exists(), "IntMessage producer should be generated") val producerContentA = producerFileA.readText() diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaClientTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaClientTest.kt deleted file mode 100644 index 7c6be337..00000000 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaClientTest.kt +++ /dev/null @@ -1,237 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.java.kafka - -import dev.banking.asyncapi.generator.core.generator.AbstractJavaGeneratorClass -import org.junit.jupiter.api.Test -import java.io.File -import kotlin.test.assertTrue - -class GenerateJavaSpringKafkaClientTest : AbstractJavaGeneratorClass() { - @Test - fun `should generate full spring kafka ecosystem for Java`() { - val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val modelPath = "dev/banking/test/userservice/v1/model" - val clientPath = "dev/banking/test/userservice/v1/client" - - val modelDir = outputDir.resolve(modelPath) - assertTrue(modelDir.resolve("UserSignedUpPayload.java").exists(), "Model UserSignedUpPayload missing") - assertTrue(modelDir.resolve("UserLoggedInPayload.java").exists(), "Model UserLoggedInPayload missing") - - val clientDir = outputDir.resolve(clientPath) - val autoconfigDir = clientDir.resolve("config") - val handlerDir = clientDir.resolve("handler") - val listenerDir = clientDir.resolve("listener") - val producerDir = clientDir.resolve("producer") - assertTrue( - listenerDir.resolve("TopicUserEventsListenerUserSignedUp.java").exists(), - "UserSignedUp Listener missing", - ) - assertTrue( - handlerDir.resolve("TopicUserEventsHandlerUserSignedUp.java").exists(), - "UserSignedUp Handler missing", - ) - assertTrue( - listenerDir.resolve("TopicUserEventsListenerUserLoggedIn.java").exists(), - "UserLoggedIn Listener missing", - ) - assertTrue( - handlerDir.resolve("TopicUserEventsHandlerUserLoggedIn.java").exists(), - "UserLoggedIn Handler missing", - ) - assertTrue(producerDir.resolve("TopicUserEventsProducerUserSignedUp.java").exists(), "UserSignedUp Producer missing") - assertTrue(producerDir.resolve("TopicUserEventsProducerUserLoggedIn.java").exists(), "UserLoggedIn Producer missing") - val userSignedUpListenerContent = listenerDir.resolve("TopicUserEventsListenerUserSignedUp.java").readText() - assertTrue( - userSignedUpListenerContent.contains("ConsumerRecord"), - "Listener should be typed to UserSignedUp", - ) - assertTrue(userSignedUpListenerContent.contains("import $modelPackage.UserSignedUpPayload;"), "Import missing") - assertTrue( - userSignedUpListenerContent.contains("import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;"), - "Missing ConditionalOnBean import", - ) - assertTrue( - userSignedUpListenerContent.contains("@ConditionalOnBean(TopicUserEventsHandlerUserSignedUp.class)"), - "Missing @ConditionalOnBean annotation", - ) - val userProducerContent = producerDir.resolve("TopicUserEventsProducerUserSignedUp.java").readText() - assertTrue( - userProducerContent.contains("@ConditionalOnProperty(name = \"kafka.topics.userEvents\")"), - "Missing @ConditionalOnProperty annotation", - ) - assertTrue( - userProducerContent.contains("@Value(\"\${kafka.topics.userEvents}\")"), - "Producer should read topic from kafka.topics.userEvents", - ) - assertTrue( - userSignedUpListenerContent.contains("@ConditionalOnProperty(name = \"kafka.topics.userEvents\")"), - "Listener should be conditional on topic property", - ) - assertTrue( - userSignedUpListenerContent.contains("@KafkaListener(topics = \"\${kafka.topics.userEvents}\""), - "Listener should read topic from kafka.topics.userEvents", - ) - assertTrue( - userSignedUpListenerContent.contains("groupId = \"\${spring.kafka.consumer.group-id}\""), - "Listener should use Spring groupId pltestholder", - ) - - val autoConfigContent = autoconfigDir.resolve("AsyncApiKafkaAutoConfiguration.java").readText() - assertTrue(autoConfigContent.contains("@ComponentScan"), "Auto-configuration should include ComponentScan") - assertTrue( - autoConfigContent.contains("basePackages = \"$clientPackage\""), - "Auto-configuration should scan the client package", - ) - - val importsFile = - File("target/generated-resources/asyncapi/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") - assertTrue(importsFile.exists(), "Auto-configuration imports file should be generated") - val importsContent = importsFile.readText() - assertTrue( - importsContent.contains("$clientPackage.config.AsyncApiKafkaAutoConfiguration"), - "Auto-configuration imports should include generated config", - ) - } - - @Test - fun `should apply custom topic property prefix for Java`() { - val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - val outputDir = File("target/generated-sources/asyncapi") - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - codegenOutputDirectory = outputDir, - kafkaTopicsPropertyPrefix = "my.property", - ) - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val producerDir = clientDir.resolve("producer") - val listenerDir = clientDir.resolve("listener") - val producerContent = producerDir.resolve("TopicUserEventsProducerUserSignedUp.java").readText() - val listenerContent = listenerDir.resolve("TopicUserEventsListenerUserSignedUp.java").readText() - assertTrue( - producerContent.contains("@Value(\"\${my.property.userEvents}\")"), - "Producer should use custom topic property key", - ) - assertTrue( - listenerContent.contains("@KafkaListener(topics = \"\${my.property.userEvents}\""), - "Listener should use custom topic property key", - ) - } - - @Test - fun `should generate full spring kafka client with native avro payload type for Java`() { - val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreated.java").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreated.java").readText() - - assertTrue(listenerContent.contains("import com.example.avro.UserCreated;")) - assertTrue(listenerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.avro.UserCreated;")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate full spring kafka client with external native avro payload type for Java`() { - val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreatedAvro.java").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreatedAvro.java").readText() - - assertTrue(listenerContent.contains("import com.example.external.avro.UserCreatedAvro;")) - assertTrue(listenerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.external.avro.UserCreatedAvro;")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate full spring kafka client with native protobuf payload type for Java`() { - val yaml = File("src/test/resources/generator/asyncapi_native_protobuf_spring_kafka_client.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreated.java").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreated.java").readText() - - assertTrue(listenerContent.contains("import com.example.protobuf.UserCreated;")) - assertTrue(listenerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.protobuf.UserCreated;")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate full spring kafka client with external native protobuf payload type for Java`() { - val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreatedProtobuf.java").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreatedProtobuf.java").readText() - - assertTrue(listenerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf;")) - assertTrue(listenerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf;")) - assertTrue(producerContent.contains("KafkaTemplate")) - } -} diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt index 9bd55837..8c9f4847 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt @@ -1,7 +1,6 @@ package dev.banking.asyncapi.generator.core.generator.java.kafka import dev.banking.asyncapi.generator.core.generator.AbstractJavaGeneratorClass -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import java.io.File import kotlin.test.assertFalse @@ -23,41 +22,6 @@ class GenerateJavaSpringKafkaOpenPayloadClientTest : AbstractJavaGeneratorClass( generateSpringKafkaClient = true, ) - val outputDir = File("target/generated-sources/asyncapi") - val modelDir = outputDir.resolve("dev/banking/test/dlq/model") - val handlerDir = outputDir.resolve("dev/banking/test/dlq/client/handler") - val listenerDir = outputDir.resolve("dev/banking/test/dlq/client/listener") - val producerDir = outputDir.resolve("dev/banking/test/dlq/client/producer") - - val modelFile = modelDir.resolve("DeadLetterQueueEventPayload.java") - assertFalse(modelFile.exists(), "Open payload should not generate a model class") - - val handlerContent = handlerDir.resolve("TopicUserDlqHandlerDeadLetterQueueEvent.java").readText() - assertTrue(handlerContent.contains("ConsumerRecord")) - - val listenerContent = listenerDir.resolve("TopicUserDlqListenerDeadLetterQueueEvent.java").readText() - assertTrue(listenerContent.contains("ConsumerRecord")) - - val producerContent = producerDir.resolve("TopicUserDlqProducerDeadLetterQueueEvent.java").readText() - assertTrue(producerContent.contains("KafkaTemplate")) - assertTrue(producerContent.contains("void sendDeadLetterQueueEvent")) - } - - @Test - fun `should use Object for open payload in spring kafka simple clients`() { - val yaml = File("src/test/resources/generator/asyncapi_open_payload_kafka_inline.yaml") - val modelPackage = "dev.banking.test.dlq.model" - val clientPackage = "dev.banking.test.dlq.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - val outputDir = File("target/generated-sources/asyncapi") val modelDir = outputDir.resolve("dev/banking/test/dlq/model") val producerDir = outputDir.resolve("dev/banking/test/dlq/client/producer") diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaSimpleTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt similarity index 82% rename from asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaSimpleTest.kt rename to asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt index 02e91da5..83a5ac00 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaSimpleTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt @@ -1,15 +1,13 @@ package dev.banking.asyncapi.generator.core.generator.java.kafka import dev.banking.asyncapi.generator.core.generator.AbstractJavaGeneratorClass -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import java.io.File -import kotlin.test.assertFalse import kotlin.test.assertTrue -class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { +class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { @Test - fun `should generate simple spring kafka client for Java`() { + fun `should generate spring kafka client for Java`() { val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -20,7 +18,6 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = true, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") @@ -29,24 +26,24 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { val consumerDir = outputDir.resolve("$clientPath/consumer") val producerFile = producerDir.resolve("UserEventsProducerUserSignedUp.java") - assertTrue(producerFile.exists(), "Simple producer should be generated") + assertTrue(producerFile.exists(), "Producer should be generated") val producerContent = producerFile.readText() assertTrue(producerContent.contains("class UserEventsProducerUserSignedUp")) assertTrue(producerContent.contains("KafkaTemplate")) assertTrue(producerContent.contains("sendUserSignedUp")) - assertTrue(!producerContent.contains("@Component"), "Simple producer should not be annotated") + assertTrue(!producerContent.contains("@Component"), "Producer should not be annotated") val consumerFile = consumerDir.resolve("UserEventsConsumer.java") - assertTrue(consumerFile.exists(), "Simple consumer should be generated") + assertTrue(consumerFile.exists(), "Consumer should be generated") val consumerContent = consumerFile.readText() assertTrue(consumerContent.contains("interface UserEventsConsumer")) assertTrue(consumerContent.contains("default void onUserSignedUp")) assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(!consumerContent.contains("@KafkaListener"), "Simple consumer should not be annotated") + assertTrue(!consumerContent.contains("@KafkaListener"), "Consumer should not be annotated") } @Test - fun `should not generate header classes for spring kafka simple client in Java`() { + fun `should generate header classes for spring kafka client in Java`() { val yaml = File("src/test/resources/generator/asyncapi_message_headers.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -57,16 +54,15 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = true, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") val headerDir = outputDir.resolve("dev/banking/test/userservice/v1/client/header") - assertFalse(headerDir.exists(), "Simple spring kafka client should not generate header classes") + assertTrue(headerDir.exists(), "Spring Kafka client should generate header classes") } @Test - fun `should generate simple spring kafka client with native avro payload type for Java`() { + fun `should generate spring kafka client with native avro payload type for Java`() { val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -77,7 +73,6 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = false, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") @@ -92,7 +87,7 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { } @Test - fun `should generate simple spring kafka client with external native avro payload type for Java`() { + fun `should generate spring kafka client with external native avro payload type for Java`() { val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -103,7 +98,6 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = false, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") @@ -118,7 +112,7 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { } @Test - fun `should generate simple spring kafka client with native protobuf payload type for Java`() { + fun `should generate spring kafka client with native protobuf payload type for Java`() { val yaml = File("src/test/resources/generator/asyncapi_native_protobuf_spring_kafka_client.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -129,7 +123,6 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = false, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") @@ -144,7 +137,7 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { } @Test - fun `should generate simple spring kafka client with external native protobuf payload type for Java`() { + fun `should generate spring kafka client with external native protobuf payload type for Java`() { val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -155,7 +148,6 @@ class GenerateJavaSpringKafkaSimpleTest : AbstractJavaGeneratorClass() { clientPackage = clientPackage, generateModels = false, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt index cf7da83e..3ba1be67 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt @@ -3,7 +3,6 @@ package dev.banking.asyncapi.generator.core.generator.kafka.spring import dev.banking.asyncapi.generator.core.fixtures.GenerationInputFixtures import dev.banking.asyncapi.generator.core.generator.model.GeneratorName import dev.banking.asyncapi.generator.core.generator.plan.GenerationTask -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Path @@ -17,7 +16,7 @@ class SpringKafkaClientGenerationTest { lateinit var tempDir: Path @Test - fun `generate delegates Kotlin simple client task to Kotlin simple generator`() { + fun `generate delegates Kotlin client task to Kotlin generator`() { val sourceOutputDirectory = tempDir.resolve("kotlin-sources").toFile() val resourceOutputDirectory = tempDir.resolve("kotlin-resources").toFile() @@ -25,7 +24,6 @@ class SpringKafkaClientGenerationTest { task = springKafkaClientTask( language = GeneratorName.KOTLIN, - clientType = SpringKafkaClientType.SIMPLE, ), generationInput = fixtures.generationInputWithUserSignupChannel(), sourceOutputDirectory = sourceOutputDirectory, @@ -41,50 +39,7 @@ class SpringKafkaClientGenerationTest { } @Test - fun `generate delegates Kotlin full client task to Kotlin full generator`() { - val sourceOutputDirectory = tempDir.resolve("kotlin-full-sources").toFile() - val resourceOutputDirectory = tempDir.resolve("kotlin-full-resources").toFile() - - generator.generate( - task = - springKafkaClientTask( - language = GeneratorName.KOTLIN, - clientType = SpringKafkaClientType.FULL, - topicPropertyPrefix = "custom.topics", - ), - generationInput = fixtures.generationInputWithUserSignupChannel(), - sourceOutputDirectory = sourceOutputDirectory, - resourceOutputDirectory = resourceOutputDirectory, - ) - - assertTrue( - sourceOutputDirectory.resolve("com/example/client/config/AsyncApiKafkaAutoConfiguration.kt").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/listener/TopicUserEventsListenerUserSignedUp.kt").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/handler/TopicUserEventsHandlerUserSignedUp.kt").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/producer/TopicUserEventsProducerUserSignedUp.kt").exists(), - ) - val producerContent = - sourceOutputDirectory - .resolve("com/example/client/producer/TopicUserEventsProducerUserSignedUp.kt") - .readText() - assertTrue( - producerContent.contains("custom.topics.userEvents"), - ) - assertTrue( - resourceOutputDirectory - .resolve("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") - .exists(), - ) - } - - @Test - fun `generate delegates Java simple client task to Java simple generator`() { + fun `generate delegates Java client task to Java generator`() { val sourceOutputDirectory = tempDir.resolve("java-sources").toFile() val resourceOutputDirectory = tempDir.resolve("java-resources").toFile() @@ -92,7 +47,6 @@ class SpringKafkaClientGenerationTest { task = springKafkaClientTask( language = GeneratorName.JAVA, - clientType = SpringKafkaClientType.SIMPLE, ), generationInput = fixtures.generationInputWithUserSignupChannel(), sourceOutputDirectory = sourceOutputDirectory, @@ -107,51 +61,12 @@ class SpringKafkaClientGenerationTest { ) } - @Test - fun `generate delegates Java full client task to Java full generator`() { - val sourceOutputDirectory = tempDir.resolve("java-full-sources").toFile() - val resourceOutputDirectory = tempDir.resolve("java-full-resources").toFile() - - generator.generate( - task = - springKafkaClientTask( - language = GeneratorName.JAVA, - clientType = SpringKafkaClientType.FULL, - ), - generationInput = fixtures.generationInputWithUserSignupChannel(), - sourceOutputDirectory = sourceOutputDirectory, - resourceOutputDirectory = resourceOutputDirectory, - ) - - assertTrue( - sourceOutputDirectory.resolve("com/example/client/config/AsyncApiKafkaAutoConfiguration.java").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/listener/TopicUserEventsListenerUserSignedUp.java").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/handler/TopicUserEventsHandlerUserSignedUp.java").exists(), - ) - assertTrue( - sourceOutputDirectory.resolve("com/example/client/producer/TopicUserEventsProducerUserSignedUp.java").exists(), - ) - assertTrue( - resourceOutputDirectory - .resolve("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") - .exists(), - ) - } - private fun springKafkaClientTask( language: GeneratorName, - clientType: SpringKafkaClientType, - topicPropertyPrefix: String = "kafka.topics", ): GenerationTask.SpringKafkaClient = GenerationTask.SpringKafkaClient( language = language, - clientType = clientType, clientPackage = "com.example.client", modelPackage = "com.example.model", - topicPropertyPrefix = topicPropertyPrefix, ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt index c7eb3d91..22d18fd2 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt @@ -1,13 +1,11 @@ package dev.banking.asyncapi.generator.core.generator.kotlin.kafka import dev.banking.asyncapi.generator.core.generator.AbstractKotlinGeneratorClass -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType import org.junit.jupiter.api.Test import java.io.File import kotlin.test.assertTrue class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorClass() { - @Test fun `should use typealias for open payload in spring kafka clients`() { val yaml = File("src/test/resources/generator/asyncapi_open_payload_kafka.yaml") @@ -22,44 +20,6 @@ class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorCl generateSpringKafkaClient = true, ) - val outputDir = File("target/generated-sources/asyncapi") - val modelDir = outputDir.resolve("dev/banking/test/dlq/model") - val handlerDir = outputDir.resolve("dev/banking/test/dlq/client/handler") - val listenerDir = outputDir.resolve("dev/banking/test/dlq/client/listener") - val producerDir = outputDir.resolve("dev/banking/test/dlq/client/producer") - - val modelFile = modelDir.resolve("DeadLetterQueueEvent.kt") - assertTrue(modelFile.exists(), "DeadLetterQueueEvent typealias should be generated") - - val handlerContent = handlerDir.resolve("TopicUserDlqHandlerDeadLetterQueueEvent.kt").readText() - assertTrue(handlerContent.contains("ConsumerRecord")) - assertTrue(handlerContent.contains("import $modelPackage.DeadLetterQueueEvent")) - - val listenerContent = listenerDir.resolve("TopicUserDlqListenerDeadLetterQueueEvent.kt").readText() - assertTrue(listenerContent.contains("ConsumerRecord")) - assertTrue(listenerContent.contains("import $modelPackage.DeadLetterQueueEvent")) - - val producerContent = producerDir.resolve("TopicUserDlqProducerDeadLetterQueueEvent.kt").readText() - assertTrue(producerContent.contains("KafkaTemplate")) - assertTrue(producerContent.contains("fun sendDeadLetterQueueEvent")) - assertTrue(producerContent.contains("import $modelPackage.DeadLetterQueueEvent")) - } - - @Test - fun `should use typealias for open payload in spring kafka simple clients`() { - val yaml = File("src/test/resources/generator/asyncapi_open_payload_kafka.yaml") - val modelPackage = "dev.banking.test.dlq.model" - val clientPackage = "dev.banking.test.dlq.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - val outputDir = File("target/generated-sources/asyncapi") val modelDir = outputDir.resolve("dev/banking/test/dlq/model") val producerDir = outputDir.resolve("dev/banking/test/dlq/client/producer") @@ -80,7 +40,7 @@ class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorCl } @Test - fun `should use typealias for open payload inline in spring kafka simple clients`() { + fun `should use typealias for open payload inline in spring kafka clients`() { val yaml = File("src/test/resources/generator/asyncapi_open_payload_kafka_inline.yaml") val modelPackage = "dev.banking.test.dlq.model" val clientPackage = "dev.banking.test.dlq.client" @@ -91,7 +51,6 @@ class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorCl clientPackage = clientPackage, generateModels = true, generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, ) val outputDir = File("target/generated-sources/asyncapi") diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt index 3bd1e3a9..c8523f55 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt @@ -38,24 +38,18 @@ class GenerateKotlinSpringKafkaOperationsTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val packagePath = packageName.replace('.', '/') assertTrue( - outputDir.resolve("$packagePath/producer/TopicEventsProducerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/producer/EventsProducerTestEvent.kt").exists(), "Producer should exist" ) assertFalse( - outputDir.resolve("$packagePath/listener/TopicEventsListenerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), "Listener should NOT exist" ) - assertFalse( - outputDir.resolve("$packagePath/handler/TopicEventsHandlerTestEvent.kt").exists(), - "Handler should NOT exist" - ) } @Test @@ -76,24 +70,18 @@ class GenerateKotlinSpringKafkaOperationsTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val packagePath = packageName.replace('.', '/') assertFalse( - outputDir.resolve("$packagePath/producer/TopicEventsProducerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/producer/EventsProducerTestEvent.kt").exists(), "Producer should NOT exist" ) assertTrue( - outputDir.resolve("$packagePath/listener/TopicEventsListenerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), "Listener should exist" ) - assertTrue( - outputDir.resolve("$packagePath/handler/TopicEventsHandlerTestEvent.kt").exists(), - "Handler should exist" - ) } @Test @@ -114,18 +102,16 @@ class GenerateKotlinSpringKafkaOperationsTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val packagePath = packageName.replace('.', '/') assertTrue( - outputDir.resolve("$packagePath/producer/TopicEventsProducerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/producer/EventsProducerTestEvent.kt").exists(), "Producer should exist" ) assertTrue( - outputDir.resolve("$packagePath/listener/TopicEventsListenerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), "Listener should exist" ) } @@ -148,18 +134,16 @@ class GenerateKotlinSpringKafkaOperationsTest { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val packagePath = packageName.replace('.', '/') assertFalse( - outputDir.resolve("$packagePath/producer/TopicEventsProducerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/producer/EventsProducerTestEvent.kt").exists(), "Producer should NOT exist" ) assertFalse( - outputDir.resolve("$packagePath/listener/TopicEventsListenerTestEvent.kt").exists(), + outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), "Listener should NOT exist" ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaSimpleTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaSimpleTest.kt deleted file mode 100644 index 7462c55a..00000000 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaSimpleTest.kt +++ /dev/null @@ -1,171 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.kotlin.kafka - -import dev.banking.asyncapi.generator.core.generator.AbstractKotlinGeneratorClass -import dev.banking.asyncapi.generator.core.generator.plan.SpringKafkaClientType -import org.junit.jupiter.api.Test -import java.io.File -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class GenerateKotlinSpringKafkaSimpleTest : AbstractKotlinGeneratorClass() { - @Test - fun `should generate simple spring kafka client`() { - val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientPath = "dev/banking/test/userservice/v1/client" - val producerDir = outputDir.resolve("$clientPath/producer") - val consumerDir = outputDir.resolve("$clientPath/consumer") - - val producerFile = producerDir.resolve("UserEventsProducerUserSignedUp.kt") - assertTrue(producerFile.exists(), "Simple producer should be generated") - val producerContent = producerFile.readText() - assertTrue(producerContent.contains("class UserEventsProducerUserSignedUp")) - assertTrue(producerContent.contains("KafkaTemplate")) - assertTrue(producerContent.contains("sendUserSignedUp")) - assertTrue(!producerContent.contains("@Component"), "Simple producer should not be annotated") - - val consumerFile = consumerDir.resolve("UserEventsConsumer.kt") - assertTrue(consumerFile.exists(), "Simple consumer should be generated") - val consumerContent = consumerFile.readText() - assertTrue(consumerContent.contains("interface UserEventsConsumer")) - assertTrue(consumerContent.contains("fun onUserSignedUp")) - assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(!consumerContent.contains("@KafkaListener"), "Simple consumer should not be annotated") - } - - @Test - fun `should not generate header classes for spring kafka simple client`() { - val yaml = File("src/test/resources/generator/asyncapi_message_headers.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = true, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val headerDir = outputDir.resolve("dev/banking/test/userservice/v1/client/header") - assertFalse(headerDir.exists(), "Simple spring kafka client should not generate header classes") - } - - @Test - fun `should generate simple spring kafka client with native avro payload type`() { - val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() - val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreated.kt").readText() - - assertTrue(consumerContent.contains("import com.example.avro.UserCreated")) - assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.avro.UserCreated")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate simple spring kafka client with external native avro payload type`() { - val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() - val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreatedAvro.kt").readText() - - assertTrue(consumerContent.contains("import com.example.external.avro.UserCreatedAvro")) - assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.external.avro.UserCreatedAvro")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate simple spring kafka client with native protobuf payload type`() { - val yaml = File("src/test/resources/generator/asyncapi_native_protobuf_spring_kafka_client.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() - val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreated.kt").readText() - - assertTrue(consumerContent.contains("import com.example.protobuf.UserCreated")) - assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.protobuf.UserCreated")) - assertTrue(producerContent.contains("KafkaTemplate")) - } - - @Test - fun `should generate simple spring kafka client with external native protobuf payload type`() { - val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") - val modelPackage = "dev.banking.test.userservice.v1.model" - val clientPackage = "dev.banking.test.userservice.v1.client" - - generateElement( - yaml = yaml, - modelPackage = modelPackage, - clientPackage = clientPackage, - generateModels = false, - generateSpringKafkaClient = true, - springKafkaClientType = SpringKafkaClientType.SIMPLE, - ) - - val outputDir = File("target/generated-sources/asyncapi") - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() - val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreatedProtobuf.kt").readText() - - assertTrue(consumerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) - assertTrue(consumerContent.contains("ConsumerRecord")) - assertTrue(producerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) - assertTrue(producerContent.contains("KafkaTemplate")) - } -} diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt index 92b3cb48..76ee1c00 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt @@ -6,9 +6,8 @@ import java.io.File import kotlin.test.assertTrue class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { - @Test - fun `should generate full spring kafka ecosystem`() { + fun `should generate spring kafka client`() { val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -22,88 +21,32 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { ) val outputDir = File("target/generated-sources/asyncapi") - val modelPath = "dev/banking/test/userservice/v1/model" val clientPath = "dev/banking/test/userservice/v1/client" - - val modelDir = outputDir.resolve(modelPath) - assertTrue(modelDir.resolve("UserSignedUpPayload.kt").exists(), "UserSignedUpPayload model missing") - assertTrue(modelDir.resolve("UserLoggedInPayload.kt").exists(), "UserLoggedInPayload model missing") - - val clientDir = outputDir.resolve(clientPath) - val autoconfigDir = clientDir.resolve("config") - val handlerDir = clientDir.resolve("handler") - val listenerDir = clientDir.resolve("listener") - val producerDir = clientDir.resolve("producer") - assertTrue( - listenerDir.resolve("TopicUserEventsListenerUserSignedUp.kt").exists(), - "UserSignedUp Listener missing", - ) - assertTrue(handlerDir.resolve("TopicUserEventsHandlerUserSignedUp.kt").exists(), "UserSignedUp Handler missing") - assertTrue( - listenerDir.resolve("TopicUserEventsListenerUserLoggedIn.kt").exists(), - "UserLoggedIn Listener missing", - ) - assertTrue(handlerDir.resolve("TopicUserEventsHandlerUserLoggedIn.kt").exists(), "UserLoggedIn Handler missing") - assertTrue(producerDir.resolve("TopicUserEventsProducerUserSignedUp.kt").exists(), "UserSignedUp Producer missing") - assertTrue(producerDir.resolve("TopicUserEventsProducerUserLoggedIn.kt").exists(), "UserLoggedIn Producer missing") - val userSignedUpListenerContent = listenerDir.resolve("TopicUserEventsListenerUserSignedUp.kt").readText() - assertTrue( - userSignedUpListenerContent.contains("ConsumerRecord"), - "Listener should be typed to UserSignedUp", - ) - assertTrue( - userSignedUpListenerContent.contains("import $modelPackage.UserSignedUp"), - "Missing correct Model Import", - ) - assertTrue( - userSignedUpListenerContent.contains("import org.springframework.boot.autoconfigure.condition.ConditionalOnBean"), - "Missing ConditionalOnBean import", - ) - assertTrue( - userSignedUpListenerContent.contains("@ConditionalOnBean(TopicUserEventsHandlerUserSignedUp::class)"), - "Missing @ConditionalOnBean annotation", - ) - val userProducerContent = producerDir.resolve("TopicUserEventsProducerUserSignedUp.kt").readText() - assertTrue( - userProducerContent.contains("@ConditionalOnProperty(name = [\"kafka.topics.userEvents\"])"), - "Missing @ConditionalOnProperty annotation", - ) - assertTrue( - userProducerContent.contains("@Value(\"\\\${kafka.topics.userEvents}\")"), - "Producer should read topic from kafka.topics.userEvents", - ) - assertTrue( - userSignedUpListenerContent.contains("@ConditionalOnProperty(name = [\"kafka.topics.userEvents\"])"), - "Listener should be conditional on topic property", - ) - assertTrue( - userSignedUpListenerContent.contains("@KafkaListener(topics = [\"\\\${kafka.topics.userEvents}\"]"), - "Listener should read topic from kafka.topics.userEvents", - ) - - val autoConfigContent = autoconfigDir.resolve("AsyncApiKafkaAutoConfiguration.kt").readText() - assertTrue(autoConfigContent.contains("@ComponentScan"), "Auto-configuration should include ComponentScan") - assertTrue( - autoConfigContent.contains("basePackages = [\"$clientPackage\"]"), - "Auto-configuration should scan the client package", - ) - - val importsFile = - File("target/generated-resources/asyncapi/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports") - assertTrue(importsFile.exists(), "Auto-configuration imports file should be generated") - val importsContent = importsFile.readText() - assertTrue( - importsContent.contains("$clientPackage.config.AsyncApiKafkaAutoConfiguration"), - "Auto-configuration imports should include generated config", - ) + val producerDir = outputDir.resolve("$clientPath/producer") + val consumerDir = outputDir.resolve("$clientPath/consumer") + + val producerFile = producerDir.resolve("UserEventsProducerUserSignedUp.kt") + assertTrue(producerFile.exists(), "Producer should be generated") + val producerContent = producerFile.readText() + assertTrue(producerContent.contains("class UserEventsProducerUserSignedUp")) + assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("sendUserSignedUp")) + assertTrue(!producerContent.contains("@Component"), "Producer should not be annotated") + + val consumerFile = consumerDir.resolve("UserEventsConsumer.kt") + assertTrue(consumerFile.exists(), "Consumer should be generated") + val consumerContent = consumerFile.readText() + assertTrue(consumerContent.contains("interface UserEventsConsumer")) + assertTrue(consumerContent.contains("fun onUserSignedUp")) + assertTrue(consumerContent.contains("ConsumerRecord")) + assertTrue(!consumerContent.contains("@KafkaListener"), "Consumer should not be annotated") } @Test - fun `should apply custom topic property prefix`() { - val yaml = File("src/test/resources/generator/asyncapi_spring_kafka_client_example.yaml") + fun `should generate header classes for spring kafka client`() { + val yaml = File("src/test/resources/generator/asyncapi_message_headers.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" - val outputDir = File("target/generated-sources/asyncapi") generateElement( yaml = yaml, @@ -111,26 +54,15 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { clientPackage = clientPackage, generateModels = true, generateSpringKafkaClient = true, - codegenOutputDirectory = outputDir, - kafkaTopicsPropertyPrefix = "my.property", - ) - val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val producerDir = clientDir.resolve("producer") - val listenerDir = clientDir.resolve("listener") - val producerContent = producerDir.resolve("TopicUserEventsProducerUserSignedUp.kt").readText() - val listenerContent = listenerDir.resolve("TopicUserEventsListenerUserSignedUp.kt").readText() - assertTrue( - producerContent.contains("@Value(\"\\\${my.property.userEvents}\")"), - "Producer should use custom topic property key", - ) - assertTrue( - listenerContent.contains("@KafkaListener(topics = [\"\\\${my.property.userEvents}\"]"), - "Listener should use custom topic property key", ) + + val outputDir = File("target/generated-sources/asyncapi") + val headerDir = outputDir.resolve("dev/banking/test/userservice/v1/client/header") + assertTrue(headerDir.exists(), "Spring Kafka client should generate header classes") } @Test - fun `should generate full spring kafka client with native avro payload type`() { + fun `should generate spring kafka client with native avro payload type`() { val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -145,17 +77,17 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { val outputDir = File("target/generated-sources/asyncapi") val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreated.kt").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreated.kt").readText() + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreated.kt").readText() - assertTrue(listenerContent.contains("import com.example.avro.UserCreated")) - assertTrue(listenerContent.contains("ConsumerRecord")) + assertTrue(consumerContent.contains("import com.example.avro.UserCreated")) + assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.avro.UserCreated")) assertTrue(producerContent.contains("KafkaTemplate")) } @Test - fun `should generate full spring kafka client with external native avro payload type`() { + fun `should generate spring kafka client with external native avro payload type`() { val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -170,17 +102,17 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { val outputDir = File("target/generated-sources/asyncapi") val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreatedAvro.kt").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreatedAvro.kt").readText() + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreatedAvro.kt").readText() - assertTrue(listenerContent.contains("import com.example.external.avro.UserCreatedAvro")) - assertTrue(listenerContent.contains("ConsumerRecord")) + assertTrue(consumerContent.contains("import com.example.external.avro.UserCreatedAvro")) + assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.avro.UserCreatedAvro")) assertTrue(producerContent.contains("KafkaTemplate")) } @Test - fun `should generate full spring kafka client with native protobuf payload type`() { + fun `should generate spring kafka client with native protobuf payload type`() { val yaml = File("src/test/resources/generator/asyncapi_native_protobuf_spring_kafka_client.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -195,17 +127,17 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { val outputDir = File("target/generated-sources/asyncapi") val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreated.kt").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreated.kt").readText() + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreated.kt").readText() - assertTrue(listenerContent.contains("import com.example.protobuf.UserCreated")) - assertTrue(listenerContent.contains("ConsumerRecord")) + assertTrue(consumerContent.contains("import com.example.protobuf.UserCreated")) + assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.protobuf.UserCreated")) assertTrue(producerContent.contains("KafkaTemplate")) } @Test - fun `should generate full spring kafka client with external native protobuf payload type`() { + fun `should generate spring kafka client with external native protobuf payload type`() { val yaml = File("src/test/resources/generator/native-assets/asyncapi_external_native_schema_assets.yaml") val modelPackage = "dev.banking.test.userservice.v1.model" val clientPackage = "dev.banking.test.userservice.v1.client" @@ -220,11 +152,11 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { val outputDir = File("target/generated-sources/asyncapi") val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") - val listenerContent = clientDir.resolve("listener/TopicUserEventsListenerUserCreatedProtobuf.kt").readText() - val producerContent = clientDir.resolve("producer/TopicUserEventsProducerUserCreatedProtobuf.kt").readText() + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + val producerContent = clientDir.resolve("producer/UserEventsProducerUserCreatedProtobuf.kt").readText() - assertTrue(listenerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) - assertTrue(listenerContent.contains("ConsumerRecord")) + assertTrue(consumerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) + assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) assertTrue(producerContent.contains("KafkaTemplate")) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt index 38e7e760..6ba638ea 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt @@ -35,12 +35,10 @@ class GeneratePrimitivePayloadTest : AbstractKotlinGeneratorClass() { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val handlerFile = - outputDir.resolve(packageName.replace('.', '/') + "/handler/TopicSimpleTopicHandlerSimpleStringMessage.kt") + outputDir.resolve(packageName.replace('.', '/') + "/consumer/SimpleTopicConsumer.kt") assertTrue(handlerFile.exists()) val content = handlerFile.readText() @@ -53,7 +51,7 @@ class GeneratePrimitivePayloadTest : AbstractKotlinGeneratorClass() { packageName.replace( '.', '/' - ) + "/producer/TopicSimpleTopicProducerSimpleStringMessage.kt" + ) + "/producer/SimpleTopicProducerSimpleStringMessage.kt" ) assertTrue(producerFile.exists(), "Producer should be generated") val producerContent = producerFile.readText() @@ -94,14 +92,12 @@ class GeneratePrimitivePayloadTest : AbstractKotlinGeneratorClass() { outputDir, packageName, packageName, - "kafka.topics", - File("target/generated-resources/asyncapi"), ) generator.generate(listOf(channel)) val producerFileA = - outputDir.resolve(packageName.replace('.', '/') + "/producer/TopicMultiTopicProducerStringMessage.kt") + outputDir.resolve(packageName.replace('.', '/') + "/producer/MultiTopicProducerStringMessage.kt") val producerFileB = - outputDir.resolve(packageName.replace('.', '/') + "/producer/TopicMultiTopicProducerIntMessage.kt") + outputDir.resolve(packageName.replace('.', '/') + "/producer/MultiTopicProducerIntMessage.kt") assertTrue(producerFileA.exists(), "StringMessage producer should be generated") assertTrue(producerFileB.exists(), "IntMessage producer should be generated") val producerContentA = producerFileA.readText() diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt index ddebe07b..ab0d9a39 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt @@ -11,7 +11,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.test.assertEquals -import kotlin.test.assertFailsWith class GenerationPlannerTest { private val planner = GenerationPlanner() @@ -129,11 +128,11 @@ class GenerationPlannerTest { } @Test - fun `plan includes header and full Spring Kafka client tasks for full client generation`() { + fun `plan includes header and Spring Kafka client tasks for Spring Kafka client generation`() { val plan = planner.plan( generatorConfiguration( - clients = listOf(springKafkaClientGeneration(clientType = SpringKafkaClientType.FULL)), + clients = listOf(springKafkaClientGeneration()), ), ) @@ -143,75 +142,7 @@ class GenerationPlannerTest { language = GeneratorName.KOTLIN, packageName = "com.example.client.header", ), - springKafkaClientTask(clientType = SpringKafkaClientType.FULL), - ), - plan.tasks, - ) - } - - @Test - fun `plan accepts explicit full Spring Kafka client type`() { - val plan = - planner.plan( - generatorConfiguration( - clients = listOf(springKafkaClientGeneration(clientType = SpringKafkaClientType.FULL)), - ), - ) - - assertEquals( - listOf( - GenerationTask.HeaderModelArtifacts( - language = GeneratorName.KOTLIN, - packageName = "com.example.client.header", - ), - springKafkaClientTask(clientType = SpringKafkaClientType.FULL), - ), - plan.tasks, - ) - } - - @Test - fun `plan excludes header model task for simple Spring Kafka client generation`() { - val plan = - planner.plan( - generatorConfiguration( - clients = listOf(springKafkaClientGeneration(clientType = SpringKafkaClientType.SIMPLE)), - ), - ) - - assertEquals( - listOf( - springKafkaClientTask(clientType = SpringKafkaClientType.SIMPLE), - ), - plan.tasks, - ) - } - - @Test - fun `plan includes custom topic prefix on Spring Kafka client task`() { - val plan = - planner.plan( - generatorConfiguration( - clients = - listOf( - springKafkaClientGeneration( - clientType = SpringKafkaClientType.FULL, - topicPropertyPrefix = "custom.topics", - ), - ), - ), - ) - - assertEquals( - listOf( - GenerationTask.HeaderModelArtifacts( - language = GeneratorName.KOTLIN, - packageName = "com.example.client.header", - ), - springKafkaClientTask( - clientType = SpringKafkaClientType.FULL, - topicPropertyPrefix = "custom.topics", - ), + springKafkaClientTask(), ), plan.tasks, ) @@ -226,7 +157,7 @@ class GenerationPlannerTest { models = ModelGeneration.Enabled(packageName = "com.example.model"), clients = listOf( - springKafkaClientGeneration(clientType = SpringKafkaClientType.FULL), + springKafkaClientGeneration(), ClientGeneration.QuarkusKafka( packageName = "com.example.client", modelPackageName = "com.example.model", @@ -247,7 +178,6 @@ class GenerationPlannerTest { ), springKafkaClientTask( language = GeneratorName.JAVA, - clientType = SpringKafkaClientType.FULL, ), GenerationTask.QuarkusKafkaClient( language = GeneratorName.JAVA, @@ -257,26 +187,6 @@ class GenerationPlannerTest { ) } - @Test - fun `plan rejects Spring Kafka client generation with blank topic prefix`() { - val exception = - assertFailsWith { - planner.plan( - generatorConfiguration( - clients = - listOf( - springKafkaClientGeneration( - clientType = SpringKafkaClientType.FULL, - topicPropertyPrefix = "", - ), - ), - ), - ) - } - - assertEquals("topicPropertyPrefix cannot be empty", exception.message) - } - private fun generatorConfiguration( language: GeneratorName = GeneratorName.KOTLIN, models: ModelGeneration = ModelGeneration.Disabled, @@ -296,30 +206,22 @@ class GenerationPlannerTest { ) private fun springKafkaClientGeneration( - clientType: SpringKafkaClientType, clientPackage: String = "com.example.client", modelPackage: String = "com.example.model", - topicPropertyPrefix: String = "kafka.topics", ): ClientGeneration.SpringKafka = ClientGeneration.SpringKafka( packageName = clientPackage, modelPackageName = modelPackage, - clientType = clientType, - topicPropertyPrefix = topicPropertyPrefix, ) private fun springKafkaClientTask( language: GeneratorName = GeneratorName.KOTLIN, - clientType: SpringKafkaClientType, clientPackage: String = "com.example.client", modelPackage: String = "com.example.model", - topicPropertyPrefix: String = "kafka.topics", ): GenerationTask.SpringKafkaClient = GenerationTask.SpringKafkaClient( language = language, - clientType = clientType, clientPackage = clientPackage, modelPackage = modelPackage, - topicPropertyPrefix = topicPropertyPrefix, ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientTypeTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientTypeTest.kt deleted file mode 100644 index 7b46333a..00000000 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/SpringKafkaClientTypeTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package dev.banking.asyncapi.generator.core.generator.plan - -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class SpringKafkaClientTypeTest { - @Test - fun `fromConfigurationValue defaults to simple when value is not configured`() { - assertEquals( - SpringKafkaClientType.SIMPLE, - SpringKafkaClientType.fromConfigurationValue( - value = null, - path = "clients.springKafka.mode", - ), - ) - } - - @Test - fun `fromConfigurationValue parses supported configuration values`() { - assertEquals( - SpringKafkaClientType.FULL, - SpringKafkaClientType.fromConfigurationValue( - value = "full", - path = "clients.springKafka.mode", - ), - ) - assertEquals( - SpringKafkaClientType.SIMPLE, - SpringKafkaClientType.fromConfigurationValue( - value = "simple", - path = "clients.springKafka.mode", - ), - ) - } - - @Test - fun `fromConfigurationValue rejects unsupported configuration values`() { - val exception = - assertFailsWith { - SpringKafkaClientType.fromConfigurationValue( - value = "basic", - path = "clients.springKafka.mode", - ) - } - - assertEquals( - "Invalid clients.springKafka.mode 'basic'. Supported values: full, simple", - exception.message, - ) - } -} diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt index bf8b3af7..177d7a6c 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt @@ -43,8 +43,6 @@ class AsyncApiPlugin : Plugin { springKafkaEnabled.set(extension.clients.springKafka.enabled) springKafkaPackageName.set(extension.clients.springKafka.packageName) springKafkaModelPackageName.set(extension.clients.springKafka.modelPackageName) - springKafkaMode.set(extension.clients.springKafka.mode) - springKafkaTopicPropertyPrefix.set(extension.clients.springKafka.topicPropertyPrefix) quarkusKafkaEnabled.set(extension.clients.quarkusKafka.enabled) quarkusKafkaPackageName.set(extension.clients.quarkusKafka.packageName) diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt index 81c3b05c..3a8e5713 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt @@ -142,8 +142,6 @@ abstract class AsyncApiSpringKafkaExtension @Inject constructor(objects: ObjectF val enabled: Property = objects.property(Boolean::class.javaObjectType) val packageName: Property = objects.property(String::class.java) val modelPackageName: Property = objects.property(String::class.java) - val mode: Property = objects.property(String::class.java) - val topicPropertyPrefix: Property = objects.property(String::class.java) } /** diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt index ad12cb23..1cb41d9c 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt @@ -86,14 +86,6 @@ abstract class GenerateAsyncApiTask : DefaultTask() { @get:Optional abstract val springKafkaModelPackageName: Property - @get:Input - @get:Optional - abstract val springKafkaMode: Property - - @get:Input - @get:Optional - abstract val springKafkaTopicPropertyPrefix: Property - @get:Input @get:Optional abstract val quarkusKafkaEnabled: Property @@ -178,8 +170,6 @@ abstract class GenerateAsyncApiTask : DefaultTask() { springKafkaEnabled = springKafkaEnabled.orNull, springKafkaPackageName = springKafkaPackageName.orNull, springKafkaModelPackageName = springKafkaModelPackageName.orNull, - springKafkaMode = springKafkaMode.orNull, - springKafkaTopicPropertyPrefix = springKafkaTopicPropertyPrefix.orNull, quarkusKafkaEnabled = quarkusKafkaEnabled.orNull, quarkusKafkaPackageName = quarkusKafkaPackageName.orNull, quarkusKafkaModelPackageName = quarkusKafkaModelPackageName.orNull, @@ -235,8 +225,6 @@ abstract class GenerateAsyncApiTask : DefaultTask() { springKafkaEnabled: Boolean?, springKafkaPackageName: String?, springKafkaModelPackageName: String?, - springKafkaMode: String?, - springKafkaTopicPropertyPrefix: String?, quarkusKafkaEnabled: Boolean?, quarkusKafkaPackageName: String?, quarkusKafkaModelPackageName: String?, @@ -247,8 +235,6 @@ abstract class GenerateAsyncApiTask : DefaultTask() { enabled = springKafkaEnabled, packageName = springKafkaPackageName, modelPackageName = springKafkaModelPackageName, - mode = springKafkaMode, - topicPropertyPrefix = springKafkaTopicPropertyPrefix, ), quarkusKafka = GeneratorConfigurationRequest.quarkusKafka( @@ -262,14 +248,10 @@ abstract class GenerateAsyncApiTask : DefaultTask() { enabled: Boolean?, packageName: String?, modelPackageName: String?, - mode: String?, - topicPropertyPrefix: String?, ): GeneratorConfigurationRequest.SpringKafka? = GeneratorConfigurationRequest.springKafka( enabled = enabled, packageName = packageName, modelPackageName = modelPackageName, - mode = mode, - topicPropertyPrefix = topicPropertyPrefix, ) } diff --git a/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt b/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt index 44bb1aed..e71db88e 100644 --- a/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt +++ b/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt @@ -124,36 +124,6 @@ class AsyncApiPluginTest { ) } - @Test - fun `should fail if spring kafka mode is invalid`() { - val projectDir = Files.createTempDirectory("gradleTest").toFile() - val yamlUrl = GradleTestHelper.resourceFile("asyncapi_kafka_complex.yaml") - File(yamlUrl.toURI()).copyTo(File(projectDir, "api.yaml")) - GradleTestHelper.writeBuildScript(projectDir, """ - plugins { id("dev.banking.asyncapi.generator") } - asyncapiGenerate { - inputFile.set(file("api.yaml")) - codegenOutputDirectory.set(layout.buildDirectory.dir("generated/asyncapi")) - generatorName.set("kotlin") - models { - packageName.set("com.example.model") - } - clients { - springKafka { - packageName.set("com.example.client") - mode.set("basic") - } - } - }""") - val result = GradleTestHelper.runGradleAndFail(projectDir, "generateAsyncApi") - assertEquals(TaskOutcome.FAILED, result.task(":generateAsyncApi")?.outcome) - assertTrue( - result.output.contains( - "Invalid clients.springKafka.mode 'basic'. Supported values: full, simple", - ), - ) - } - @Test fun `should fail if java model type is invalid`() { val projectDir = Files.createTempDirectory("gradleTest").toFile() diff --git a/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt b/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt index 02d39ffa..ef62b784 100644 --- a/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt +++ b/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt @@ -120,16 +120,12 @@ class MavenSpringKafkaConfiguration { var enabled: Boolean? = null var packageName: String? = null var modelPackageName: String? = null - var mode: String? = null - var topicPropertyPrefix: String? = null fun toRequest(): GeneratorConfigurationRequest.SpringKafka? = GeneratorConfigurationRequest.springKafka( enabled = enabled, packageName = packageName, modelPackageName = modelPackageName, - mode = mode, - topicPropertyPrefix = topicPropertyPrefix, ) } diff --git a/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml index 6207fb42..d146386d 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml @@ -64,7 +64,6 @@ true - full diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml index 8a49f964..4ad838d0 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml @@ -27,7 +27,6 @@ com.example.simple.client - simple diff --git a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt index 0ca4835c..2bc3a7ce 100644 --- a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt +++ b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt @@ -270,22 +270,6 @@ class AsyncApiGeneratorMojoTest { ) } - @Test - fun `should fail when spring kafka mode is invalid`() { - val mojo = AsyncApiGeneratorMojo().apply { - project(MavenProject()) - inputFile(inputPath("asyncapi_valid_content_kotlin.yaml")) - codegenOutputDirectory(outputPath("target/should-fail-client-mode")) - resourceOutputDirectory(outputPath("target/generated-resources/asyncapi")) - models(models(packageName = "com.fail")) - clients(clients(springKafka = springKafka(packageName = "com.fail.client", mode = "basic"))) - generatorName("kotlin") - } - assertThrows { - mojo.execute() - } - } - @Test fun `should fail when java model type is invalid`() { val mojo = AsyncApiGeneratorMojo().apply { diff --git a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt index 73df1297..2234d85e 100644 --- a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt +++ b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt @@ -122,15 +122,11 @@ object MavenTestHelper { fun springKafka( packageName: String? = null, modelPackageName: String? = null, - mode: String? = null, - topicPropertyPrefix: String? = null, enabled: Boolean? = null, ): MavenSpringKafkaConfiguration = MavenSpringKafkaConfiguration().apply { this.packageName = packageName this.modelPackageName = modelPackageName - this.mode = mode - this.topicPropertyPrefix = topicPropertyPrefix this.enabled = enabled } From 56b60d9781f8c71297de565032c95ba44d33a7f3 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Wed, 17 Jun 2026 15:44:59 +0200 Subject: [PATCH 02/10] Kafka client capability that owns the Spring Kafka sub-capability --- .../configuration/ClientGeneration.kt | 21 +++++- .../GeneratorConfigurationFactory.kt | 41 +++++++---- .../GeneratorConfigurationRequest.kt | 70 +++++++++++++++++-- .../factory/JavaSpringKafkaModelFactory.kt | 6 +- .../kafka/spring/JavaSpringKafkaGenerator.kt | 10 ++- .../spring/SpringKafkaClientGeneration.kt | 4 ++ .../factory/KotlinSpringKafkaModelFactory.kt | 6 +- .../spring/KotlinSpringKafkaGenerator.kt | 10 ++- .../core/generator/plan/GenerationPlanner.kt | 34 +++++---- .../core/generator/plan/GenerationTask.kt | 2 + 10 files changed, 165 insertions(+), 39 deletions(-) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt index 641bc116..2c9a0621 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/ClientGeneration.kt @@ -7,11 +7,30 @@ package dev.banking.asyncapi.generator.core.generator.configuration * - `GenerationPlannerTest` */ sealed interface ClientGeneration { - data class SpringKafka( + data class Kafka( val packageName: String, val modelPackageName: String, + val headers: Headers = Headers(), + val springKafka: SpringKafka? = null, ) : ClientGeneration + data class Headers( + val enabled: Boolean = true, + ) + + data class SpringKafka( + val producer: Producer = Producer(), + val consumer: Consumer = Consumer(), + ) + + data class Producer( + val enabled: Boolean = true, + ) + + data class Consumer( + val enabled: Boolean = true, + ) + data class QuarkusKafka( val packageName: String, val modelPackageName: String, diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt index 441c9c8b..2df14ddd 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactory.kt @@ -52,18 +52,35 @@ object GeneratorConfigurationFactory { }, clients = buildList { - request.clients.springKafka?.let { springKafka -> + request.clients.kafka?.let { kafka -> add( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = requiredPackageName( - path = "clients.springKafka.packageName", - value = springKafka.packageName, + path = "clients.kafka.packageName", + value = kafka.packageName, ), modelPackageName = requiredClientModelPackageName( - path = "clients.springKafka.modelPackageName", - configuredModelPackageName = springKafka.modelPackageName, + path = "clients.kafka.modelPackageName", + configuredModelPackageName = kafka.modelPackageName, modelsPackageName = request.models?.packageName, ), + headers = + ClientGeneration.Headers( + enabled = kafka.headers.enabled, + ), + springKafka = + kafka.springKafka?.let { springKafka -> + ClientGeneration.SpringKafka( + producer = + ClientGeneration.Producer( + enabled = springKafka.producer.enabled, + ), + consumer = + ClientGeneration.Consumer( + enabled = springKafka.consumer.enabled, + ), + ) + }, ), ) } @@ -106,9 +123,9 @@ object GeneratorConfigurationFactory { ) } - if (request.clients.springKafka != null && request.clients.springKafka.packageName == null) { + if (request.clients.kafka != null && request.clients.kafka.packageName == null) { throw IllegalArgumentException( - "clients.springKafka.packageName is required when clients.springKafka is configured", + "clients.kafka.packageName is required when clients.kafka is configured", ) } @@ -127,12 +144,12 @@ object GeneratorConfigurationFactory { value = request.schemas.avroProjection?.packageName, ) validatePackageName( - path = "clients.springKafka.packageName", - value = request.clients.springKafka?.packageName, + path = "clients.kafka.packageName", + value = request.clients.kafka?.packageName, ) validatePackageName( - path = "clients.springKafka.modelPackageName", - value = request.clients.springKafka?.modelPackageName, + path = "clients.kafka.modelPackageName", + value = request.clients.kafka?.modelPackageName, ) validatePackageName( path = "clients.quarkusKafka.packageName", diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt index 735756dd..e3b9d8d5 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequest.kt @@ -47,13 +47,32 @@ data class GeneratorConfigurationRequest( ) data class Clients( - val springKafka: SpringKafka? = null, + val kafka: Kafka? = null, val quarkusKafka: QuarkusKafka? = null, ) - data class SpringKafka( + data class Kafka( val packageName: String? = null, val modelPackageName: String? = null, + val headers: KafkaHeaders = KafkaHeaders(), + val springKafka: KafkaSpringKafka? = null, + ) + + data class KafkaHeaders( + val enabled: Boolean = true, + ) + + data class KafkaSpringKafka( + val producer: KafkaProducer = KafkaProducer(), + val consumer: KafkaConsumer = KafkaConsumer(), + ) + + data class KafkaProducer( + val enabled: Boolean = true, + ) + + data class KafkaConsumer( + val enabled: Boolean = true, ) data class QuarkusKafka( @@ -116,23 +135,62 @@ data class GeneratorConfigurationRequest( else -> null } - fun springKafka( + fun kafka( enabled: Boolean? = null, packageName: String? = null, modelPackageName: String? = null, - ): SpringKafka? = + headers: KafkaHeaders? = null, + springKafka: KafkaSpringKafka? = null, + ): Kafka? = when { enabled == false -> null enabled == true || packageName != null || - modelPackageName != null -> - SpringKafka( + modelPackageName != null || + headers != null || + springKafka != null -> + Kafka( packageName = packageName, modelPackageName = modelPackageName, + headers = headers ?: KafkaHeaders(), + springKafka = springKafka, ) else -> null } + fun kafkaHeaders(enabled: Boolean? = null): KafkaHeaders? = + when (enabled) { + null -> null + else -> KafkaHeaders(enabled = enabled) + } + + fun kafkaSpringKafka( + enabled: Boolean? = null, + producer: KafkaProducer? = null, + consumer: KafkaConsumer? = null, + ): KafkaSpringKafka? = + when { + enabled == false -> null + enabled == true || producer != null || consumer != null -> + KafkaSpringKafka( + producer = producer ?: KafkaProducer(), + consumer = consumer ?: KafkaConsumer(), + ) + else -> null + } + + fun kafkaProducer(enabled: Boolean? = null): KafkaProducer? = + when (enabled) { + null -> null + else -> KafkaProducer(enabled = enabled) + } + + fun kafkaConsumer(enabled: Boolean? = null): KafkaConsumer? = + when (enabled) { + null -> null + else -> KafkaConsumer(enabled = enabled) + } + fun quarkusKafka( enabled: Boolean? = null, packageName: String? = null, diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index 612b55b2..e4f749b5 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -14,6 +14,8 @@ import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface class JavaSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, + private val generateProducers: Boolean = true, + private val generateConsumers: Boolean = true, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), ) { fun create(channel: AnalyzedChannel): List { @@ -26,7 +28,7 @@ class JavaSpringKafkaModelFactory( val baseImports = payloads.mapNotNull { payload -> payload.importName } - if (channel.isConsumer) { + if (channel.isConsumer && generateConsumers) { val consumerName = "${baseName}Consumer" val imports = (baseImports + "org.apache.kafka.clients.consumer.ConsumerRecord").distinct().sorted() val methods = @@ -47,7 +49,7 @@ class JavaSpringKafkaModelFactory( ) } - if (channel.isProducer) { + if (channel.isProducer && generateProducers) { val imports = (baseImports + "org.apache.kafka.clients.producer.ProducerRecord" + "org.springframework.kafka.core.KafkaTemplate") .distinct() diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt index b536d096..fa395027 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt @@ -9,8 +9,16 @@ class JavaSpringKafkaGenerator( outputDir: File, clientPackage: String, modelPackage: String, + generateProducers: Boolean = true, + generateConsumers: Boolean = true, ) { - private val modelFactory = JavaSpringKafkaModelFactory(clientPackage, modelPackage) + private val modelFactory = + JavaSpringKafkaModelFactory( + clientPackage = clientPackage, + modelPackage = modelPackage, + generateProducers = generateProducers, + generateConsumers = generateConsumers, + ) private val producerGenerator = JavaSpringKafkaProducerGenerator(outputDir) private val consumerGenerator = JavaSpringKafkaConsumerGenerator(outputDir) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt index c87c732f..ec4af5b8 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt @@ -38,6 +38,8 @@ class SpringKafkaClientGeneration { outputDir = sourceOutputDirectory, clientPackage = task.clientPackage, modelPackage = task.modelPackage, + generateProducers = task.generateProducers, + generateConsumers = task.generateConsumers, ) kafkaGenerator.generate(generationInput.channels) } @@ -52,6 +54,8 @@ class SpringKafkaClientGeneration { outputDir = sourceOutputDirectory, clientPackage = task.clientPackage, modelPackage = task.modelPackage, + generateProducers = task.generateProducers, + generateConsumers = task.generateConsumers, ) kafkaGenerator.generate(generationInput.channels) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index 5a5028b5..2bef9f81 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -12,6 +12,8 @@ import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryT class KotlinSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, + private val generateProducers: Boolean = true, + private val generateConsumers: Boolean = true, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), ) { fun create(channel: AnalyzedChannel): List { @@ -24,7 +26,7 @@ class KotlinSpringKafkaModelFactory( val baseImports = payloads.mapNotNull { payload -> payload.importName } - if (channel.isConsumer) { + if (channel.isConsumer && generateConsumers) { val consumerName = "${baseName}Consumer" val imports = (baseImports + "org.apache.kafka.clients.consumer.ConsumerRecord").distinct().sorted() val methods = @@ -46,7 +48,7 @@ class KotlinSpringKafkaModelFactory( ) } - if (channel.isProducer) { + if (channel.isProducer && generateProducers) { val imports = (baseImports + "org.apache.kafka.clients.producer.ProducerRecord" + "org.springframework.kafka.core.KafkaTemplate") .distinct() diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt index b21d44fa..328ea728 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt @@ -9,8 +9,16 @@ class KotlinSpringKafkaGenerator( outputDir: File, clientPackage: String, modelPackage: String, + generateProducers: Boolean = true, + generateConsumers: Boolean = true, ) { - private val modelFactory = KotlinSpringKafkaModelFactory(clientPackage, modelPackage) + private val modelFactory = + KotlinSpringKafkaModelFactory( + clientPackage = clientPackage, + modelPackage = modelPackage, + generateProducers = generateProducers, + generateConsumers = generateConsumers, + ) private val producerGenerator = KotlinSpringKafkaProducerGenerator(outputDir) private val consumerGenerator = KotlinSpringKafkaConsumerGenerator(outputDir) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt index 222b184e..6560f791 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt @@ -30,20 +30,26 @@ class GenerationPlanner { configuration.clients.forEach { client -> when (client) { - is ClientGeneration.SpringKafka -> { - add( - GenerationTask.HeaderModelArtifacts( - language = configuration.language, - packageName = "${client.packageName}.header", - ), - ) - add( - GenerationTask.SpringKafkaClient( - language = configuration.language, - clientPackage = client.packageName, - modelPackage = client.modelPackageName, - ), - ) + is ClientGeneration.Kafka -> { + if (client.headers.enabled) { + add( + GenerationTask.HeaderModelArtifacts( + language = configuration.language, + packageName = "${client.packageName}.header", + ), + ) + } + client.springKafka?.let { springKafka -> + add( + GenerationTask.SpringKafkaClient( + language = configuration.language, + clientPackage = client.packageName, + modelPackage = client.modelPackageName, + generateProducers = springKafka.producer.enabled, + generateConsumers = springKafka.consumer.enabled, + ), + ) + } } is ClientGeneration.QuarkusKafka -> add(GenerationTask.QuarkusKafkaClient(configuration.language)) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt index a2dfbe16..da75930a 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt @@ -26,6 +26,8 @@ sealed interface GenerationTask { val language: GeneratorName, val clientPackage: String, val modelPackage: String, + val generateProducers: Boolean = true, + val generateConsumers: Boolean = true, ) : GenerationTask data class QuarkusKafkaClient( From 1731b1790246381c74c29cf38b21b2ba25bc5e22 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Wed, 17 Jun 2026 16:01:04 +0200 Subject: [PATCH 03/10] client configuration nested under Kafka --- README.md | 23 ++++-- .../banking/asyncapi/generator/cli/Main.kt | 70 ++++++++++++---- .../generator/cli/AsyncApiGeneratorCliTest.kt | 14 ++-- .../generator/AbstractJavaGeneratorClass.kt | 3 +- .../generator/AbstractKotlinGeneratorClass.kt | 3 +- .../AsyncApiGeneratorOutputContractTest.kt | 3 +- .../GeneratorConfigurationFactoryTest.kt | 82 +++++++++++++++---- .../GeneratorConfigurationRequestTest.kt | 44 ++++++++-- .../spring/SpringKafkaClientGenerationTest.kt | 30 +++++++ .../generator/plan/GenerationPlannerTest.kt | 73 +++++++++++++++-- .../generator/gradle/plugin/AsyncApiPlugin.kt | 10 ++- .../plugin/extensions/AsyncApiExtension.kt | 76 +++++++++++++++-- .../plugin/tasks/GenerateAsyncApiTask.kt | 73 +++++++++++++---- .../gradle/plugin/AsyncApiPluginTest.kt | 23 ++++-- .../plugin/MavenGeneratorConfiguration.kt | 74 +++++++++++++++-- .../src/test/it/config-merge/pom.xml | 18 ++-- .../src/test/it/per-execution-only/pom.xml | 7 +- .../src/test/it/spring-kafka-simple/pom.xml | 7 +- .../maven/plugin/AsyncApiGeneratorMojoTest.kt | 10 +-- .../generator/maven/plugin/MavenTestHelper.kt | 40 +++++++-- 20 files changed, 565 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index cc6e48a8..44515f4e 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,14 @@ Example usage in your `pom.xml`: my.package.path.model - + my.package.path.client my.package.path.model - + + true + + @@ -77,10 +80,13 @@ asyncapiGenerate { packageName.set("my.package.path.model") } clients { - springKafka { + kafka { packageName.set("my.package.path.client") // Optional: defaults to models.packageName when models are configured modelPackageName.set("my.package.path.model") + springKafka { + enabled.set(true) + } } } schemas { @@ -109,10 +115,13 @@ asyncapiGenerate { packageName = 'my.package.path.model' } clients { - springKafka { + kafka { packageName = 'my.package.path.client' // Optional: defaults to models.packageName when models are configured modelPackageName = 'my.package.path.model' + springKafka { + enabled = true + } } } schemas { @@ -298,9 +307,11 @@ Generated Java Protobuf message sources are produced by running `protoc` during ### Spring Kafka Clients -Spring Kafka output is configured under `clients.springKafka`. +Spring Kafka output is configured under `clients.kafka.springKafka`. -Generated Spring Kafka clients use `models.packageName` for payload model types by default. If models are generated elsewhere, configure `clients.springKafka.modelPackageName` to point the client API at that package without generating model output in the same execution. +Generated Spring Kafka clients use `models.packageName` for payload model types by default. If models are generated elsewhere, configure `clients.kafka.modelPackageName` to point the client API at that package without generating model output in the same execution. + +Kafka client configuration can also be narrowed by capability. `clients.kafka.headers.enabled` controls typed header model generation, and `clients.kafka.springKafka.producer.enabled` / `clients.kafka.springKafka.consumer.enabled` control whether producer and consumer artifacts are generated. For native Avro message payloads, generated Spring Kafka clients use the Java type declared by the Avro schema namespace and name. For example, a native Avro schema with `namespace: com.example.avro` and `name: UserCreated` is used as `com.example.avro.UserCreated` in generated producer and consumer APIs. diff --git a/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt b/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt index 79b3d437..f2192e7f 100644 --- a/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt +++ b/asyncapi-generator-cli/src/main/kotlin/dev/banking/asyncapi/generator/cli/Main.kt @@ -93,19 +93,48 @@ class AsyncApiGeneratorCli : CliktCommand(name = "asyncapi-generator") { "false" to false, ) - private val clientsSpringKafka by option( - "--clients-spring-kafka", - help = "Enable Spring Kafka client generation", + private val clientsKafka by option( + "--clients-kafka", + help = "Enable Kafka client generation", ).flag(default = false) - private val clientsSpringKafkaPackage by option( - "--clients-spring-kafka-package", - help = "Package for generated Spring Kafka clients", + private val clientsKafkaPackage by option( + "--clients-kafka-package", + help = "Package for generated Kafka clients", ) - private val clientsSpringKafkaModelPackage by option( - "--clients-spring-kafka-model-package", - help = "Package containing model types used by generated Spring Kafka clients", + private val clientsKafkaModelPackage by option( + "--clients-kafka-model-package", + help = "Package containing model types used by generated Kafka clients", + ) + + private val clientsKafkaHeaders by option( + "--clients-kafka-headers", + help = "Generate typed Kafka header models when headers are defined (default: true)", + ).choice( + "true" to true, + "false" to false, + ) + + private val clientsKafkaSpringKafka by option( + "--clients-kafka-spring-kafka", + help = "Enable Spring Kafka client generation under Kafka clients", + ).flag(default = false) + + private val clientsKafkaSpringKafkaProducer by option( + "--clients-kafka-spring-kafka-producer", + help = "Generate Spring Kafka producer APIs (default: true)", + ).choice( + "true" to true, + "false" to false, + ) + + private val clientsKafkaSpringKafkaConsumer by option( + "--clients-kafka-spring-kafka-consumer", + help = "Generate Spring Kafka consumer APIs (default: true)", + ).choice( + "true" to true, + "false" to false, ) private val clientsQuarkusKafka by option( @@ -206,11 +235,24 @@ class AsyncApiGeneratorCli : CliktCommand(name = "asyncapi-generator") { private fun clientRequest(): GeneratorConfigurationRequest.Clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.springKafka( - enabled = true.takeIf { clientsSpringKafka }, - packageName = clientsSpringKafkaPackage, - modelPackageName = clientsSpringKafkaModelPackage, + kafka = + GeneratorConfigurationRequest.kafka( + enabled = true.takeIf { clientsKafka }, + packageName = clientsKafkaPackage, + modelPackageName = clientsKafkaModelPackage, + headers = GeneratorConfigurationRequest.kafkaHeaders(enabled = clientsKafkaHeaders), + springKafka = + GeneratorConfigurationRequest.kafkaSpringKafka( + enabled = true.takeIf { clientsKafkaSpringKafka }, + producer = + GeneratorConfigurationRequest.kafkaProducer( + enabled = clientsKafkaSpringKafkaProducer, + ), + consumer = + GeneratorConfigurationRequest.kafkaConsumer( + enabled = clientsKafkaSpringKafkaConsumer, + ), + ), ), quarkusKafka = GeneratorConfigurationRequest.quarkusKafka( diff --git a/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt b/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt index 17c9c599..815137e1 100644 --- a/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt +++ b/asyncapi-generator-cli/src/test/kotlin/dev/banking/asyncapi/generator/cli/AsyncApiGeneratorCliTest.kt @@ -26,7 +26,8 @@ class AsyncApiGeneratorCliTest { "--codegen-output", codegenDir.absolutePath, "--resource-output", resourceDir.absolutePath, "--models-package", "com.example.cli.model", - "--clients-spring-kafka-package", "com.example.cli.client", + "--clients-kafka-package", "com.example.cli.client", + "--clients-kafka-spring-kafka", "--generator", "kotlin", ) ) @@ -45,8 +46,9 @@ class AsyncApiGeneratorCliTest { "--input", inputFile.absolutePath, "--codegen-output", codegenDir.absolutePath, "--resource-output", resourceDir.absolutePath, - "--clients-spring-kafka-package", "com.example.cli.client", - "--clients-spring-kafka-model-package", "com.example.cli.model", + "--clients-kafka-package", "com.example.cli.client", + "--clients-kafka-model-package", "com.example.cli.model", + "--clients-kafka-spring-kafka", "--generator", "kotlin", ) ) @@ -195,7 +197,7 @@ class AsyncApiGeneratorCliTest { } @Test - fun `should fail if spring kafka client is enabled without client package`(@TempDir tempDir: Path) { + fun `should fail if kafka spring kafka client is enabled without client package`(@TempDir tempDir: Path) { val inputFile = File("src/test/resources/asyncapi_kafka_complex.yaml") val codegenDir = tempDir.resolve("codegen").toFile() val exception = @@ -205,14 +207,14 @@ class AsyncApiGeneratorCliTest { "-i", inputFile.absolutePath, "--codegen-output", codegenDir.absolutePath, "--models-package", "com.example.cli.model", - "--clients-spring-kafka", + "--clients-kafka-spring-kafka", ) ) } assertTrue( exception.message.orEmpty().contains( - "clients.springKafka.packageName is required when clients.springKafka is configured", + "clients.kafka.packageName is required when clients.kafka is configured", ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt index 072df530..1a4d9deb 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt @@ -53,9 +53,10 @@ abstract class AbstractJavaGeneratorClass { buildList { if (generateSpringKafkaClient) { add( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, + springKafka = ClientGeneration.SpringKafka(), ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt index 24b6248e..c9df0383 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt @@ -50,9 +50,10 @@ abstract class AbstractKotlinGeneratorClass { buildList { if (generateSpringKafkaClient) { add( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, + springKafka = ClientGeneration.SpringKafka(), ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt index 19d5e4e9..e2452abf 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AsyncApiGeneratorOutputContractTest.kt @@ -245,9 +245,10 @@ class AsyncApiGeneratorOutputContractTest { resourceOutputDirectory = resourceOutputDirectory, clients = listOf( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = "com.example.kafka", modelPackageName = "com.example.model", + springKafka = ClientGeneration.SpringKafka(), ), ), ), diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt index bed684d8..c011660e 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationFactoryTest.kt @@ -77,16 +77,17 @@ class GeneratorConfigurationFactoryTest { } @Test - fun `create enables Spring Kafka client generation when client package is configured`() { + fun `create enables Kafka and Spring Kafka client generation when client package is configured`() { val configuration = GeneratorConfigurationFactory.create( request( models = GeneratorConfigurationRequest.Models(packageName = "com.example.model"), clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( + kafka = + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ), ), @@ -94,9 +95,10 @@ class GeneratorConfigurationFactoryTest { assertEquals( listOf( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = "com.example.client", modelPackageName = "com.example.model", + springKafka = ClientGeneration.SpringKafka(), ), ), configuration.clients, @@ -110,10 +112,11 @@ class GeneratorConfigurationFactoryTest { request( clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( + kafka = + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", modelPackageName = "com.example.external.model", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ), ), @@ -121,9 +124,49 @@ class GeneratorConfigurationFactoryTest { assertEquals( listOf( - ClientGeneration.SpringKafka( + ClientGeneration.Kafka( packageName = "com.example.client", modelPackageName = "com.example.external.model", + springKafka = ClientGeneration.SpringKafka(), + ), + ), + configuration.clients, + ) + } + + @Test + fun `create maps kafka header and spring kafka generation options`() { + val configuration = + GeneratorConfigurationFactory.create( + request( + models = GeneratorConfigurationRequest.Models(packageName = "com.example.model"), + clients = + GeneratorConfigurationRequest.Clients( + kafka = + GeneratorConfigurationRequest.Kafka( + packageName = "com.example.client", + headers = GeneratorConfigurationRequest.KafkaHeaders(enabled = false), + springKafka = + GeneratorConfigurationRequest.KafkaSpringKafka( + producer = GeneratorConfigurationRequest.KafkaProducer(enabled = false), + consumer = GeneratorConfigurationRequest.KafkaConsumer(enabled = true), + ), + ), + ), + ), + ) + + assertEquals( + listOf( + ClientGeneration.Kafka( + packageName = "com.example.client", + modelPackageName = "com.example.model", + headers = ClientGeneration.Headers(enabled = false), + springKafka = + ClientGeneration.SpringKafka( + producer = ClientGeneration.Producer(enabled = false), + consumer = ClientGeneration.Consumer(enabled = true), + ), ), ), configuration.clients, @@ -213,14 +256,14 @@ class GeneratorConfigurationFactoryTest { request( clients = GeneratorConfigurationRequest.Clients( - springKafka = GeneratorConfigurationRequest.SpringKafka(), + kafka = GeneratorConfigurationRequest.Kafka(), ), ), ) } assertEquals( - "clients.springKafka.packageName is required when clients.springKafka is configured", + "clients.kafka.packageName is required when clients.kafka is configured", exception.message, ) } @@ -233,9 +276,10 @@ class GeneratorConfigurationFactoryTest { request( clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( + kafka = + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ), ), @@ -243,7 +287,7 @@ class GeneratorConfigurationFactoryTest { } assertEquals( - "clients.springKafka.modelPackageName is required when models.packageName is not configured", + "clients.kafka.modelPackageName is required when models.packageName is not configured", exception.message, ) } @@ -346,15 +390,16 @@ class GeneratorConfigurationFactoryTest { ), ) assertConfigurationError( - expectedMessage = "clients.springKafka.packageName cannot be empty", + expectedMessage = "clients.kafka.packageName cannot be empty", request = request( clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( + kafka = + GeneratorConfigurationRequest.Kafka( packageName = " ", modelPackageName = "com.example.model", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ), ), @@ -373,16 +418,17 @@ class GeneratorConfigurationFactoryTest { ) assertConfigurationError( expectedMessage = - "clients.springKafka.modelPackageName must be a dot-separated package name, " + + "clients.kafka.modelPackageName must be a dot-separated package name, " + "for example com.example.model", request = request( clients = GeneratorConfigurationRequest.Clients( - springKafka = - GeneratorConfigurationRequest.SpringKafka( + kafka = + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", modelPackageName = "com.example.", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ), ), diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt index 3f408853..4ab60496 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/configuration/GeneratorConfigurationRequestTest.kt @@ -82,35 +82,63 @@ class GeneratorConfigurationRequestTest { } @Test - fun `spring kafka request is created only when client output is configured`() { - assertNull(GeneratorConfigurationRequest.springKafka()) + fun `kafka request is created only when client output is configured`() { + assertNull(GeneratorConfigurationRequest.kafka()) assertNull( - GeneratorConfigurationRequest.springKafka( + GeneratorConfigurationRequest.kafka( enabled = false, packageName = "com.example.client", modelPackageName = "com.example.model", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ) assertEquals( - GeneratorConfigurationRequest.SpringKafka( + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", modelPackageName = "com.example.model", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), - GeneratorConfigurationRequest.springKafka( + GeneratorConfigurationRequest.kafka( packageName = "com.example.client", modelPackageName = "com.example.model", + springKafka = GeneratorConfigurationRequest.KafkaSpringKafka(), ), ) } @Test - fun `spring kafka request can be created from package only`() { + fun `kafka request can be created from package only`() { assertEquals( - GeneratorConfigurationRequest.SpringKafka( + GeneratorConfigurationRequest.Kafka( packageName = "com.example.client", ), - GeneratorConfigurationRequest.springKafka(packageName = "com.example.client"), + GeneratorConfigurationRequest.kafka(packageName = "com.example.client"), + ) + } + + @Test + fun `spring kafka request is created only when kafka spring kafka output is configured`() { + assertNull(GeneratorConfigurationRequest.kafkaSpringKafka()) + assertNull( + GeneratorConfigurationRequest.kafkaSpringKafka( + enabled = false, + producer = GeneratorConfigurationRequest.KafkaProducer(enabled = false), + consumer = GeneratorConfigurationRequest.KafkaConsumer(enabled = false), + ), + ) + + assertEquals( + GeneratorConfigurationRequest.KafkaSpringKafka(), + GeneratorConfigurationRequest.kafkaSpringKafka(enabled = true), + ) + assertEquals( + GeneratorConfigurationRequest.KafkaSpringKafka( + producer = GeneratorConfigurationRequest.KafkaProducer(enabled = false), + ), + GeneratorConfigurationRequest.kafkaSpringKafka( + producer = GeneratorConfigurationRequest.KafkaProducer(enabled = false), + ), ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt index 3ba1be67..e7d4c620 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt @@ -6,6 +6,7 @@ import dev.banking.asyncapi.generator.core.generator.plan.GenerationTask import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Path +import kotlin.test.assertFalse import kotlin.test.assertTrue class SpringKafkaClientGenerationTest { @@ -61,12 +62,41 @@ class SpringKafkaClientGenerationTest { ) } + @Test + fun `generate respects producer and consumer task options`() { + val sourceOutputDirectory = tempDir.resolve("configured-sources").toFile() + val resourceOutputDirectory = tempDir.resolve("configured-resources").toFile() + + generator.generate( + task = + springKafkaClientTask( + language = GeneratorName.KOTLIN, + generateProducers = false, + generateConsumers = true, + ), + generationInput = fixtures.generationInputWithUserSignupChannel(), + sourceOutputDirectory = sourceOutputDirectory, + resourceOutputDirectory = resourceOutputDirectory, + ) + + assertFalse( + sourceOutputDirectory.resolve("com/example/client/producer/UserEventsProducerUserSignedUp.kt").exists(), + ) + assertTrue( + sourceOutputDirectory.resolve("com/example/client/consumer/UserEventsConsumer.kt").exists(), + ) + } + private fun springKafkaClientTask( language: GeneratorName, + generateProducers: Boolean = true, + generateConsumers: Boolean = true, ): GenerationTask.SpringKafkaClient = GenerationTask.SpringKafkaClient( language = language, clientPackage = "com.example.client", modelPackage = "com.example.model", + generateProducers = generateProducers, + generateConsumers = generateConsumers, ) } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt index ab0d9a39..060091d2 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt @@ -132,7 +132,7 @@ class GenerationPlannerTest { val plan = planner.plan( generatorConfiguration( - clients = listOf(springKafkaClientGeneration()), + clients = listOf(kafkaClientGeneration()), ), ) @@ -148,6 +148,61 @@ class GenerationPlannerTest { ) } + @Test + fun `plan can disable Kafka header generation`() { + val plan = + planner.plan( + generatorConfiguration( + clients = + listOf( + kafkaClientGeneration( + headers = ClientGeneration.Headers(enabled = false), + ), + ), + ), + ) + + assertEquals( + listOf( + springKafkaClientTask(), + ), + plan.tasks, + ) + } + + @Test + fun `plan includes Spring Kafka producer and consumer options on Spring Kafka client task`() { + val plan = + planner.plan( + generatorConfiguration( + clients = + listOf( + kafkaClientGeneration( + springKafka = + ClientGeneration.SpringKafka( + producer = ClientGeneration.Producer(enabled = false), + consumer = ClientGeneration.Consumer(enabled = true), + ), + ), + ), + ), + ) + + assertEquals( + listOf( + GenerationTask.HeaderModelArtifacts( + language = GeneratorName.KOTLIN, + packageName = "com.example.client.header", + ), + springKafkaClientTask( + generateProducers = false, + generateConsumers = true, + ), + ), + plan.tasks, + ) + } + @Test fun `plan uses selected language for language-specific tasks`() { val plan = @@ -157,7 +212,7 @@ class GenerationPlannerTest { models = ModelGeneration.Enabled(packageName = "com.example.model"), clients = listOf( - springKafkaClientGeneration(), + kafkaClientGeneration(), ClientGeneration.QuarkusKafka( packageName = "com.example.client", modelPackageName = "com.example.model", @@ -205,23 +260,31 @@ class GenerationPlannerTest { clients = clients, ) - private fun springKafkaClientGeneration( + private fun kafkaClientGeneration( clientPackage: String = "com.example.client", modelPackage: String = "com.example.model", - ): ClientGeneration.SpringKafka = - ClientGeneration.SpringKafka( + headers: ClientGeneration.Headers = ClientGeneration.Headers(), + springKafka: ClientGeneration.SpringKafka? = ClientGeneration.SpringKafka(), + ): ClientGeneration.Kafka = + ClientGeneration.Kafka( packageName = clientPackage, modelPackageName = modelPackage, + headers = headers, + springKafka = springKafka, ) private fun springKafkaClientTask( language: GeneratorName = GeneratorName.KOTLIN, clientPackage: String = "com.example.client", modelPackage: String = "com.example.model", + generateProducers: Boolean = true, + generateConsumers: Boolean = true, ): GenerationTask.SpringKafkaClient = GenerationTask.SpringKafkaClient( language = language, clientPackage = clientPackage, modelPackage = modelPackage, + generateProducers = generateProducers, + generateConsumers = generateConsumers, ) } diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt index 177d7a6c..ebe2b71a 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPlugin.kt @@ -40,9 +40,13 @@ class AsyncApiPlugin : Plugin { nativeProtobufEnabled.set(extension.schemas.nativeProtobuf.enabled) nativeProtobufGenerateJavaMessageTypes.set(extension.schemas.nativeProtobuf.generateJavaMessageTypes) - springKafkaEnabled.set(extension.clients.springKafka.enabled) - springKafkaPackageName.set(extension.clients.springKafka.packageName) - springKafkaModelPackageName.set(extension.clients.springKafka.modelPackageName) + kafkaEnabled.set(extension.clients.kafka.enabled) + kafkaPackageName.set(extension.clients.kafka.packageName) + kafkaModelPackageName.set(extension.clients.kafka.modelPackageName) + kafkaHeadersEnabled.set(extension.clients.kafka.headers.enabled) + kafkaSpringKafkaEnabled.set(extension.clients.kafka.springKafka.enabled) + kafkaSpringKafkaProducerEnabled.set(extension.clients.kafka.springKafka.producer.enabled) + kafkaSpringKafkaConsumerEnabled.set(extension.clients.kafka.springKafka.consumer.enabled) quarkusKafkaEnabled.set(extension.clients.quarkusKafka.enabled) quarkusKafkaPackageName.set(extension.clients.quarkusKafka.packageName) diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt index 3a8e5713..6e551bee 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/extensions/AsyncApiExtension.kt @@ -118,13 +118,13 @@ abstract class AsyncApiNativeProtobufExtension @Inject constructor(objects: Obje * - `AsyncApiPluginTest` */ abstract class AsyncApiClientsExtension @Inject constructor(objects: ObjectFactory) { - val springKafka: AsyncApiSpringKafkaExtension = - objects.newInstance(AsyncApiSpringKafkaExtension::class.java) + val kafka: AsyncApiKafkaExtension = + objects.newInstance(AsyncApiKafkaExtension::class.java) val quarkusKafka: AsyncApiQuarkusKafkaExtension = objects.newInstance(AsyncApiQuarkusKafkaExtension::class.java) - fun springKafka(action: Action) { - action.execute(springKafka) + fun kafka(action: Action) { + action.execute(kafka) } fun quarkusKafka(action: Action) { @@ -133,15 +133,79 @@ abstract class AsyncApiClientsExtension @Inject constructor(objects: ObjectFacto } /** - * Gradle Spring Kafka client configuration. + * Gradle Kafka client configuration. * * Expected behavior is covered by: * - `AsyncApiPluginTest` */ -abstract class AsyncApiSpringKafkaExtension @Inject constructor(objects: ObjectFactory) { +abstract class AsyncApiKafkaExtension @Inject constructor(objects: ObjectFactory) { val enabled: Property = objects.property(Boolean::class.javaObjectType) val packageName: Property = objects.property(String::class.java) val modelPackageName: Property = objects.property(String::class.java) + val headers: AsyncApiKafkaHeadersExtension = + objects.newInstance(AsyncApiKafkaHeadersExtension::class.java) + val springKafka: AsyncApiKafkaSpringKafkaExtension = + objects.newInstance(AsyncApiKafkaSpringKafkaExtension::class.java) + + fun headers(action: Action) { + action.execute(headers) + } + + fun springKafka(action: Action) { + action.execute(springKafka) + } +} + +/** + * Gradle Kafka header generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiPluginTest` + */ +abstract class AsyncApiKafkaHeadersExtension @Inject constructor(objects: ObjectFactory) { + val enabled: Property = objects.property(Boolean::class.javaObjectType) +} + +/** + * Gradle Spring Kafka client generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiPluginTest` + */ +abstract class AsyncApiKafkaSpringKafkaExtension @Inject constructor(objects: ObjectFactory) { + val enabled: Property = objects.property(Boolean::class.javaObjectType) + val producer: AsyncApiKafkaProducerExtension = + objects.newInstance(AsyncApiKafkaProducerExtension::class.java) + val consumer: AsyncApiKafkaConsumerExtension = + objects.newInstance(AsyncApiKafkaConsumerExtension::class.java) + + fun producer(action: Action) { + action.execute(producer) + } + + fun consumer(action: Action) { + action.execute(consumer) + } +} + +/** + * Gradle Spring Kafka producer generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiPluginTest` + */ +abstract class AsyncApiKafkaProducerExtension @Inject constructor(objects: ObjectFactory) { + val enabled: Property = objects.property(Boolean::class.javaObjectType) +} + +/** + * Gradle Spring Kafka consumer generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiPluginTest` + */ +abstract class AsyncApiKafkaConsumerExtension @Inject constructor(objects: ObjectFactory) { + val enabled: Property = objects.property(Boolean::class.javaObjectType) } /** diff --git a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt index 1cb41d9c..a449946f 100644 --- a/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt +++ b/asyncapi-generator-gradle-plugin/src/main/kotlin/dev/banking/asyncapi/generator/gradle/plugin/tasks/GenerateAsyncApiTask.kt @@ -76,15 +76,31 @@ abstract class GenerateAsyncApiTask : DefaultTask() { @get:Input @get:Optional - abstract val springKafkaEnabled: Property + abstract val kafkaEnabled: Property @get:Input @get:Optional - abstract val springKafkaPackageName: Property + abstract val kafkaPackageName: Property @get:Input @get:Optional - abstract val springKafkaModelPackageName: Property + abstract val kafkaModelPackageName: Property + + @get:Input + @get:Optional + abstract val kafkaHeadersEnabled: Property + + @get:Input + @get:Optional + abstract val kafkaSpringKafkaEnabled: Property + + @get:Input + @get:Optional + abstract val kafkaSpringKafkaProducerEnabled: Property + + @get:Input + @get:Optional + abstract val kafkaSpringKafkaConsumerEnabled: Property @get:Input @get:Optional @@ -167,9 +183,13 @@ abstract class GenerateAsyncApiTask : DefaultTask() { ), clients = clientRequest( - springKafkaEnabled = springKafkaEnabled.orNull, - springKafkaPackageName = springKafkaPackageName.orNull, - springKafkaModelPackageName = springKafkaModelPackageName.orNull, + kafkaEnabled = kafkaEnabled.orNull, + kafkaPackageName = kafkaPackageName.orNull, + kafkaModelPackageName = kafkaModelPackageName.orNull, + kafkaHeadersEnabled = kafkaHeadersEnabled.orNull, + kafkaSpringKafkaEnabled = kafkaSpringKafkaEnabled.orNull, + kafkaSpringKafkaProducerEnabled = kafkaSpringKafkaProducerEnabled.orNull, + kafkaSpringKafkaConsumerEnabled = kafkaSpringKafkaConsumerEnabled.orNull, quarkusKafkaEnabled = quarkusKafkaEnabled.orNull, quarkusKafkaPackageName = quarkusKafkaPackageName.orNull, quarkusKafkaModelPackageName = quarkusKafkaModelPackageName.orNull, @@ -222,19 +242,27 @@ abstract class GenerateAsyncApiTask : DefaultTask() { ) private fun clientRequest( - springKafkaEnabled: Boolean?, - springKafkaPackageName: String?, - springKafkaModelPackageName: String?, + kafkaEnabled: Boolean?, + kafkaPackageName: String?, + kafkaModelPackageName: String?, + kafkaHeadersEnabled: Boolean?, + kafkaSpringKafkaEnabled: Boolean?, + kafkaSpringKafkaProducerEnabled: Boolean?, + kafkaSpringKafkaConsumerEnabled: Boolean?, quarkusKafkaEnabled: Boolean?, quarkusKafkaPackageName: String?, quarkusKafkaModelPackageName: String?, ): GeneratorConfigurationRequest.Clients = GeneratorConfigurationRequest.Clients( - springKafka = - springKafkaRequest( - enabled = springKafkaEnabled, - packageName = springKafkaPackageName, - modelPackageName = springKafkaModelPackageName, + kafka = + kafkaRequest( + enabled = kafkaEnabled, + packageName = kafkaPackageName, + modelPackageName = kafkaModelPackageName, + headersEnabled = kafkaHeadersEnabled, + springKafkaEnabled = kafkaSpringKafkaEnabled, + springKafkaProducerEnabled = kafkaSpringKafkaProducerEnabled, + springKafkaConsumerEnabled = kafkaSpringKafkaConsumerEnabled, ), quarkusKafka = GeneratorConfigurationRequest.quarkusKafka( @@ -244,14 +272,25 @@ abstract class GenerateAsyncApiTask : DefaultTask() { ), ) - private fun springKafkaRequest( + private fun kafkaRequest( enabled: Boolean?, packageName: String?, modelPackageName: String?, - ): GeneratorConfigurationRequest.SpringKafka? = - GeneratorConfigurationRequest.springKafka( + headersEnabled: Boolean?, + springKafkaEnabled: Boolean?, + springKafkaProducerEnabled: Boolean?, + springKafkaConsumerEnabled: Boolean?, + ): GeneratorConfigurationRequest.Kafka? = + GeneratorConfigurationRequest.kafka( enabled = enabled, packageName = packageName, modelPackageName = modelPackageName, + headers = GeneratorConfigurationRequest.kafkaHeaders(enabled = headersEnabled), + springKafka = + GeneratorConfigurationRequest.kafkaSpringKafka( + enabled = springKafkaEnabled, + producer = GeneratorConfigurationRequest.kafkaProducer(enabled = springKafkaProducerEnabled), + consumer = GeneratorConfigurationRequest.kafkaConsumer(enabled = springKafkaConsumerEnabled), + ), ) } diff --git a/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt b/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt index e71db88e..7add6b0c 100644 --- a/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt +++ b/asyncapi-generator-gradle-plugin/src/test/kotlin/dev/banking/asyncapi/generator/gradle/plugin/AsyncApiPluginTest.kt @@ -110,8 +110,10 @@ class AsyncApiPluginTest { packageName.set("com.example.model") } clients { - springKafka { - enabled.set(true) + kafka { + springKafka { + enabled.set(true) + } } } }""") @@ -119,7 +121,7 @@ class AsyncApiPluginTest { assertEquals(TaskOutcome.FAILED, result.task(":generateAsyncApi")?.outcome) assertTrue( result.output.contains( - "clients.springKafka.packageName is required when clients.springKafka is configured", + "clients.kafka.packageName is required when clients.kafka is configured", ), ) } @@ -230,8 +232,11 @@ class AsyncApiPluginTest { packageName.set("com.example.kafka.model") } clients { - springKafka { + kafka { packageName.set("com.example.kafka.client") + springKafka { + enabled.set(true) + } } } }""" @@ -261,9 +266,12 @@ class AsyncApiPluginTest { codegenOutputDirectory.set(layout.buildDirectory.dir("generated/asyncapi")) generatorName.set("kotlin") clients { - springKafka { + kafka { packageName.set("com.example.kafka.client") modelPackageName.set("com.example.kafka.model") + springKafka { + enabled.set(true) + } } } }""" @@ -297,8 +305,11 @@ class AsyncApiPluginTest { packageName.set("com.example.kafka.model") } clients { - springKafka { + kafka { packageName.set("com.example.kafka.client") + springKafka { + enabled.set(true) + } } } }""" diff --git a/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt b/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt index ef62b784..3c648b8d 100644 --- a/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt +++ b/asyncapi-generator-maven-plugin/src/main/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenGeneratorConfiguration.kt @@ -100,35 +100,97 @@ class MavenNativeProtobufConfiguration { * - `AsyncApiGeneratorMojoTest` */ class MavenClientGenerationConfiguration { - var springKafka: MavenSpringKafkaConfiguration? = null + var kafka: MavenKafkaConfiguration? = null var quarkusKafka: MavenQuarkusKafkaConfiguration? = null fun toRequest(): GeneratorConfigurationRequest.Clients = GeneratorConfigurationRequest.Clients( - springKafka = springKafka?.toRequest(), + kafka = kafka?.toRequest(), quarkusKafka = quarkusKafka?.toRequest(), ) } /** - * Maven Spring Kafka client configuration. + * Maven Kafka client configuration. * * Expected behavior is covered by: * - `AsyncApiGeneratorMojoTest` */ -class MavenSpringKafkaConfiguration { +class MavenKafkaConfiguration { var enabled: Boolean? = null var packageName: String? = null var modelPackageName: String? = null + var headers: MavenKafkaHeadersConfiguration? = null + var springKafka: MavenKafkaSpringKafkaConfiguration? = null - fun toRequest(): GeneratorConfigurationRequest.SpringKafka? = - GeneratorConfigurationRequest.springKafka( + fun toRequest(): GeneratorConfigurationRequest.Kafka? = + GeneratorConfigurationRequest.kafka( enabled = enabled, packageName = packageName, modelPackageName = modelPackageName, + headers = headers?.toRequest(), + springKafka = springKafka?.toRequest(), + ) +} + +/** + * Maven Kafka header generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiGeneratorMojoTest` + */ +class MavenKafkaHeadersConfiguration { + var enabled: Boolean? = null + + fun toRequest(): GeneratorConfigurationRequest.KafkaHeaders? = + GeneratorConfigurationRequest.kafkaHeaders(enabled = enabled) +} + +/** + * Maven Spring Kafka client generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiGeneratorMojoTest` + */ +class MavenKafkaSpringKafkaConfiguration { + var enabled: Boolean? = null + var producer: MavenKafkaProducerConfiguration? = null + var consumer: MavenKafkaConsumerConfiguration? = null + + fun toRequest(): GeneratorConfigurationRequest.KafkaSpringKafka? = + GeneratorConfigurationRequest.kafkaSpringKafka( + enabled = enabled ?: true, + producer = producer?.toRequest(), + consumer = consumer?.toRequest(), ) } +/** + * Maven Spring Kafka producer generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiGeneratorMojoTest` + */ +class MavenKafkaProducerConfiguration { + var enabled: Boolean? = null + + fun toRequest(): GeneratorConfigurationRequest.KafkaProducer? = + GeneratorConfigurationRequest.kafkaProducer(enabled = enabled) +} + +/** + * Maven Spring Kafka consumer generation configuration. + * + * Expected behavior is covered by: + * - `AsyncApiGeneratorMojoTest` + */ +class MavenKafkaConsumerConfiguration { + var enabled: Boolean? = null + + fun toRequest(): GeneratorConfigurationRequest.KafkaConsumer? = + GeneratorConfigurationRequest.kafkaConsumer(enabled = enabled) +} + /** * Maven Quarkus Kafka client configuration. * diff --git a/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml index d146386d..1e2f0ff9 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/config-merge/pom.xml @@ -24,9 +24,12 @@ com.example.a.model - + com.example.a.client - + + true + + @@ -47,9 +50,9 @@ com.example.b.model - + false - + @@ -62,9 +65,12 @@ kotlin - + true - + + true + + diff --git a/asyncapi-generator-maven-plugin/src/test/it/per-execution-only/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/per-execution-only/pom.xml index a4437c1b..25d7b48e 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/per-execution-only/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/per-execution-only/pom.xml @@ -24,9 +24,12 @@ com.example.a.model - + com.example.a.client - + + true + + diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml index 4ad838d0..285d8ba9 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml @@ -25,9 +25,12 @@ com.example.simple.model - + com.example.simple.client - + + true + + diff --git a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt index 2bc3a7ce..0ba41994 100644 --- a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt +++ b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/AsyncApiGeneratorMojoTest.kt @@ -6,6 +6,7 @@ import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.codegenOutput import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.generatorName import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.inputPath import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.javaSourceOutputDirectory +import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.kafka import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.nativeAvro import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.nativeProtobuf import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.outputPath @@ -15,7 +16,6 @@ import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.outputFile import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.project import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.resourceOutputDirectory import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.schemas -import dev.banking.asyncapi.generator.maven.plugin.MavenTestHelper.springKafka import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.project.MavenProject import org.junit.jupiter.api.Assertions.assertEquals @@ -50,7 +50,7 @@ class AsyncApiGeneratorMojoTest { codegenOutputDirectory(outputPath("target/generated-sources/asyncapi")) resourceOutputDirectory(outputPath("target/generated-resources/asyncapi")) models(models(packageName = "com.example.kafka.model")) - clients(clients(springKafka = springKafka(packageName = "com.example.kafka.client"))) + clients(clients(kafka = kafka(packageName = "com.example.kafka.client"))) generatorName("kotlin") }.execute() val clientDir = File("target/generated-sources/asyncapi/com/example/kafka/client") @@ -65,7 +65,7 @@ class AsyncApiGeneratorMojoTest { codegenOutputDirectory(outputPath("target/generated-sources/asyncapi")) resourceOutputDirectory(outputPath("target/generated-resources/asyncapi")) models(models(packageName = "com.example.kafka.model")) - clients(clients(springKafka = springKafka(packageName = "com.example.kafka.client"))) + clients(clients(kafka = kafka(packageName = "com.example.kafka.client"))) generatorName("java") }.execute() val clientDir = File("target/generated-sources/asyncapi/com/example/kafka/client") @@ -98,8 +98,8 @@ class AsyncApiGeneratorMojoTest { resourceOutputDirectory(outputPath("target/generated-resources/asyncapi-client-only")) clients( clients( - springKafka = - springKafka( + kafka = + kafka( packageName = "com.example.kafka.client", modelPackageName = "com.example.kafka.model", ), diff --git a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt index 2234d85e..14116665 100644 --- a/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt +++ b/asyncapi-generator-maven-plugin/src/test/kotlin/dev/banking/asyncapi/generator/maven/plugin/MavenTestHelper.kt @@ -111,23 +111,53 @@ object MavenTestHelper { } fun clients( - springKafka: MavenSpringKafkaConfiguration? = null, + kafka: MavenKafkaConfiguration? = null, quarkusKafka: MavenQuarkusKafkaConfiguration? = null, ): MavenClientGenerationConfiguration = MavenClientGenerationConfiguration().apply { - this.springKafka = springKafka + this.kafka = kafka this.quarkusKafka = quarkusKafka } - fun springKafka( + fun kafka( packageName: String? = null, modelPackageName: String? = null, enabled: Boolean? = null, - ): MavenSpringKafkaConfiguration = - MavenSpringKafkaConfiguration().apply { + headers: MavenKafkaHeadersConfiguration? = null, + springKafka: MavenKafkaSpringKafkaConfiguration? = springKafka(), + ): MavenKafkaConfiguration = + MavenKafkaConfiguration().apply { this.packageName = packageName this.modelPackageName = modelPackageName this.enabled = enabled + this.headers = headers + this.springKafka = springKafka + } + + fun kafkaHeaders(enabled: Boolean? = null): MavenKafkaHeadersConfiguration = + MavenKafkaHeadersConfiguration().apply { + this.enabled = enabled + } + + fun springKafka( + enabled: Boolean? = null, + producer: MavenKafkaProducerConfiguration? = null, + consumer: MavenKafkaConsumerConfiguration? = null, + ): MavenKafkaSpringKafkaConfiguration = + MavenKafkaSpringKafkaConfiguration().apply { + this.enabled = enabled + this.producer = producer + this.consumer = consumer + } + + fun kafkaProducer(enabled: Boolean? = null): MavenKafkaProducerConfiguration = + MavenKafkaProducerConfiguration().apply { + this.enabled = enabled + } + + fun kafkaConsumer(enabled: Boolean? = null): MavenKafkaConsumerConfiguration = + MavenKafkaConsumerConfiguration().apply { + this.enabled = enabled } fun quarkusKafka( From 900cab31ed71f4afd862cc7ceada79368d4c4bba Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 22:56:45 +0200 Subject: [PATCH 04/10] Add typed headers to Spring Kafka generated APIs Introduce shared message header analysis so generated Spring Kafka producers and consumers can use typed header DTOs when AsyncAPI message headers are defined. The Spring Kafka model preparation now carries header type metadata through the analyzer, payload model, Java/Kotlin factories, and templates. Generated producer and consumer APIs now include typed header parameters for messages with headers, while no-header messages keep the existing simpler signatures. Producer templates now map non-null typed header values into Kafka record headers, and consumer templates expose the generated header type in handler signatures. Add tests covering header analysis, Java Spring Kafka generation, and Kotlin Spring Kafka generation so typed header imports, method signatures, and producer header mapping remain stable. --- .../generator/analyzer/AnalyzedMessage.kt | 1 + .../analyzer/AnalyzedMessageHeaders.kt | 8 ++ .../analyzer/AnalyzedMultiFormatMessage.kt | 1 + .../generator/analyzer/ChannelAnalyzer.kt | 15 +++- .../analyzer/MessageHeaderAnalyzer.kt | 73 +++++++++++++++++++ .../factory/JavaSpringKafkaModelFactory.kt | 61 +++++++++++++++- .../generator/java/model/GeneratorItem.kt | 14 +++- .../generator/kafka/spring/KafkaPayload.kt | 8 ++ .../factory/KotlinSpringKafkaModelFactory.kt | 51 ++++++++++++- .../generator/kotlin/model/GeneratorItem.kt | 14 +++- .../generator/loader/HeaderSchemaCollector.kt | 52 +++---------- .../java/spring-kafka-consumer.mustache | 2 +- .../java/spring-kafka-producer.mustache | 13 +++- .../kotlin/spring-kafka-consumer.mustache | 2 +- .../kotlin/spring-kafka-producer.mustache | 11 ++- .../generator/analyzer/ChannelAnalyzerTest.kt | 34 +++++++++ .../java/kafka/GenerateJavaSpringKafkaTest.kt | 26 +++++++ .../kafka/GenerateKotlinSpringKafkaTest.kt | 24 ++++++ 18 files changed, 354 insertions(+), 56 deletions(-) create mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessageHeaders.kt create mode 100644 asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/MessageHeaderAnalyzer.kt diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessage.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessage.kt index 4c4c17f6..94e17da4 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessage.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessage.kt @@ -7,4 +7,5 @@ data class AnalyzedMessage( val payloadTypeName: String, // The payload type name (e.g. "UserSignedUpPayload") val schema: Schema, // The payload schema val keySchema: Schema? = null, // Optional Kafka Key schema + val headers: AnalyzedMessageHeaders? = null, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessageHeaders.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessageHeaders.kt new file mode 100644 index 00000000..b0f90d7a --- /dev/null +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMessageHeaders.kt @@ -0,0 +1,8 @@ +package dev.banking.asyncapi.generator.core.generator.analyzer + +import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface + +data class AnalyzedMessageHeaders( + val typeName: String, + val properties: Map, +) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMultiFormatMessage.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMultiFormatMessage.kt index 9b43dc08..bceeb990 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMultiFormatMessage.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/AnalyzedMultiFormatMessage.kt @@ -15,4 +15,5 @@ data class AnalyzedMultiFormatMessage( val messageName: String, val payloadName: String, val schema: MultiFormatSchema, + val headers: AnalyzedMessageHeaders? = null, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzer.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzer.kt index 9c1c9c08..be26ee29 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzer.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzer.kt @@ -63,7 +63,7 @@ class ChannelAnalyzer { val usage = channelUsage[name]!! val finalProducer = if (!usage.isProducer && !usage.isConsumer) true else usage.isProducer val finalConsumer = if (!usage.isProducer && !usage.isConsumer) true else usage.isConsumer - val resolvedMessages = resolveMessages(channel.messages) + val resolvedMessages = resolveMessages(channelName = name, messages = channel.messages) AnalyzedChannel( channelName = name, @@ -78,7 +78,10 @@ class ChannelAnalyzer { return ChannelAnalysisResult(analyzedChannels) } - private fun resolveMessages(messages: Map?): ResolvedMessages { + private fun resolveMessages( + channelName: String, + messages: Map?, + ): ResolvedMessages { if (messages.isNullOrEmpty()) return ResolvedMessages() val analyzedMessages = mutableListOf() val analyzedMultiFormatMessages = mutableListOf() @@ -95,6 +98,12 @@ class ChannelAnalyzer { var typeName: String? = null val baseName = MapperUtil.toPascalCase(message.name ?: message.title ?: name) val inlinePayloadTypeName = if (baseName.endsWith("Payload")) baseName else "${baseName}Payload" + val headers = + MessageHeaderAnalyzer.analyze( + channelName = channelName, + messageKey = name, + message = message, + ) when (val p = message.payload) { is SchemaInterface.SchemaInline -> { @@ -123,6 +132,7 @@ class ChannelAnalyzer { messageName = baseName, payloadTypeName = typeName, schema = payloadSchema, + headers = headers, ), ) } else if (multiFormatSchema != null) { @@ -131,6 +141,7 @@ class ChannelAnalyzer { messageName = baseName, payloadName = typeName, schema = multiFormatSchema, + headers = headers, ), ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/MessageHeaderAnalyzer.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/MessageHeaderAnalyzer.kt new file mode 100644 index 00000000..c5dada80 --- /dev/null +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/MessageHeaderAnalyzer.kt @@ -0,0 +1,73 @@ +package dev.banking.asyncapi.generator.core.generator.analyzer + +import dev.banking.asyncapi.generator.core.generator.util.MapperUtil +import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryType +import dev.banking.asyncapi.generator.core.model.messages.Message +import dev.banking.asyncapi.generator.core.model.messages.MessageTrait +import dev.banking.asyncapi.generator.core.model.messages.MessageTraitInterface +import dev.banking.asyncapi.generator.core.model.schemas.Schema +import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface + +/** + * Resolves generated header DTO metadata from AsyncAPI message headers. + * + * Expected behavior is covered by: + * - `ChannelAnalyzerTest` + * - `JavaModelPreparerTest` + * - `KotlinModelPreparerTest` + */ +object MessageHeaderAnalyzer { + fun analyze( + channelName: String, + messageKey: String, + message: Message, + ): AnalyzedMessageHeaders? { + val properties = collectProperties(message) + if (properties.isEmpty()) return null + + val channelNamePascal = MapperUtil.toPascalCase(channelName) + val messageName = message.name ?: message.title ?: messageKey + val messageNamePascal = MapperUtil.toPascalCase(messageName) + + return AnalyzedMessageHeaders( + typeName = "Topic${channelNamePascal}Headers$messageNamePascal", + properties = properties, + ) + } + + private fun collectProperties(message: Message): Map { + val headers = mutableMapOf() + + message.traits?.forEach { traitInterface -> + val trait = + when (traitInterface) { + is MessageTraitInterface.InlineMessageTrait -> traitInterface.trait + is MessageTraitInterface.ReferenceMessageTrait -> traitInterface.reference.model as? MessageTrait + } + trait?.headers?.let { headers.putAll(extractProperties(it)) } + } + + message.headers?.let { headers.putAll(extractProperties(it)) } + + return headers + } + + private fun extractProperties(schemaInterface: SchemaInterface): Map = + when (schemaInterface) { + is SchemaInterface.SchemaInline -> + if (schemaInterface.schema.type.getPrimaryType() == "object") { + schemaInterface.schema.properties ?: emptyMap() + } else { + emptyMap() + } + is SchemaInterface.SchemaReference -> { + val schema = schemaInterface.reference.model as? Schema + if (schema?.type.getPrimaryType() == "object") { + schema?.properties ?: emptyMap() + } else { + emptyMap() + } + } + else -> emptyMap() + } +} diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index e4f749b5..6c5dd67b 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -2,6 +2,8 @@ package dev.banking.asyncapi.generator.core.generator.java.factory import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessage +import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessageHeaders +import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaHeaderProperty import dev.banking.asyncapi.generator.core.generator.java.model.GeneratorItem import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaPayload import dev.banking.asyncapi.generator.core.generator.kafka.spring.NativeKafkaPayloadResolver @@ -26,7 +28,9 @@ class JavaSpringKafkaModelFactory( val payloads = channel.payloads() val baseImports = - payloads.mapNotNull { payload -> payload.importName } + payloads.flatMap { payload -> listOfNotNull(payload.importName, payload.headerImportName) } + .distinct() + .sorted() if (channel.isConsumer && generateConsumers) { val consumerName = "${baseName}Consumer" @@ -36,6 +40,7 @@ class JavaSpringKafkaModelFactory( GeneratorItem.HandlerMethod( methodName = "on${payload.messageName}", payloadType = payload.payloadType, + headerType = payload.headerTypeName, ) } items.add( @@ -51,7 +56,12 @@ class JavaSpringKafkaModelFactory( if (channel.isProducer && generateProducers) { val imports = - (baseImports + "org.apache.kafka.clients.producer.ProducerRecord" + "org.springframework.kafka.core.KafkaTemplate") + ( + baseImports + + "org.apache.kafka.clients.producer.ProducerRecord" + + "org.springframework.kafka.core.KafkaTemplate" + + listOfNotNull("java.nio.charset.StandardCharsets".takeIf { payloads.any { it.headerProperties.isNotEmpty() } }) + ) .distinct() .sorted() payloads.forEach { payload -> @@ -59,6 +69,14 @@ class JavaSpringKafkaModelFactory( GeneratorItem.SendMethod( methodName = "send${payload.messageName}", payloadType = payload.payloadType, + headerType = payload.headerTypeName, + headerProperties = + payload.headerProperties.map { header -> + GeneratorItem.HeaderProperty( + name = header.name, + accessorName = header.accessorName, + ) + }, ) val producerName = "${baseName}Producer${payload.messageName}" items.add( @@ -80,7 +98,11 @@ class JavaSpringKafkaModelFactory( } private fun AnalyzedChannel.payloads(): List = - messages.map(::payload) + multiFormatMessages.mapNotNull(nativeKafkaPayloadResolver::resolve) + messages.map(::payload) + + multiFormatMessages.mapNotNull { message -> + nativeKafkaPayloadResolver.resolve(message) + ?.withHeaders(message.headers) + } private fun payload(msg: AnalyzedMessage): KafkaPayload { val type = resolvePayloadType(msg) @@ -93,6 +115,19 @@ class JavaSpringKafkaModelFactory( } else { "$modelPackage.$type" }, + headerTypeName = msg.headers?.typeName, + headerImportName = msg.headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + msg.headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = getterName(headerName), + ) + } + .orEmpty(), ) } @@ -133,4 +168,24 @@ class JavaSpringKafkaModelFactory( private fun isPrimitive(type: String): Boolean = type in setOf("String", "Integer", "Long", "Boolean", "Double", "java.math.BigDecimal", "Object") + + private fun KafkaPayload.withHeaders(headers: AnalyzedMessageHeaders?): KafkaPayload = + copy( + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = getterName(headerName), + ) + } + .orEmpty(), + ) + + private fun getterName(propertyName: String): String = + "get" + propertyName.replaceFirstChar { it.uppercase() } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt index f0004887..e7861b3f 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt @@ -68,10 +68,22 @@ sealed interface GeneratorItem { data class HandlerMethod( val methodName: String, val payloadType: String, - ) + val headerType: String? = null, + ) { + val hasHeaders: Boolean get() = headerType != null + } data class SendMethod( val methodName: String, val payloadType: String, + val headerType: String? = null, + val headerProperties: List = emptyList(), + ) { + val hasHeaders: Boolean get() = headerType != null + } + + data class HeaderProperty( + val name: String, + val accessorName: String, ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/KafkaPayload.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/KafkaPayload.kt index a487d88f..0b86aac6 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/KafkaPayload.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/KafkaPayload.kt @@ -7,4 +7,12 @@ data class KafkaPayload( val messageName: String, val payloadType: String, val importName: String? = null, + val headerTypeName: String? = null, + val headerImportName: String? = null, + val headerProperties: List = emptyList(), +) + +data class KafkaHeaderProperty( + val name: String, + val accessorName: String, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index 2bef9f81..b5ac697a 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -2,6 +2,8 @@ package dev.banking.asyncapi.generator.core.generator.kotlin.factory import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedChannel import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessage +import dev.banking.asyncapi.generator.core.generator.analyzer.AnalyzedMessageHeaders +import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaHeaderProperty import dev.banking.asyncapi.generator.core.generator.kafka.spring.KafkaPayload import dev.banking.asyncapi.generator.core.generator.kafka.spring.NativeKafkaPayloadResolver import dev.banking.asyncapi.generator.core.generator.kotlin.model.GeneratorItem @@ -24,7 +26,9 @@ class KotlinSpringKafkaModelFactory( val payloads = channel.payloads() val baseImports = - payloads.mapNotNull { payload -> payload.importName } + payloads.flatMap { payload -> listOfNotNull(payload.importName, payload.headerImportName) } + .distinct() + .sorted() if (channel.isConsumer && generateConsumers) { val consumerName = "${baseName}Consumer" @@ -35,6 +39,7 @@ class KotlinSpringKafkaModelFactory( methodName = "on${payload.messageName}", payloadType = payload.payloadType, keyType = "String?", + headerType = payload.headerTypeName, ) } items.add( @@ -59,6 +64,14 @@ class KotlinSpringKafkaModelFactory( methodName = "send${payload.messageName}", payloadType = payload.payloadType, keyType = "String", + headerType = payload.headerTypeName, + headerProperties = + payload.headerProperties.map { header -> + GeneratorItem.HeaderProperty( + name = header.name, + accessorName = header.accessorName, + ) + }, ) val producerName = "${baseName}Producer${payload.messageName}" items.add( @@ -80,7 +93,11 @@ class KotlinSpringKafkaModelFactory( } private fun AnalyzedChannel.payloads(): List = - messages.map(::payload) + multiFormatMessages.mapNotNull(nativeKafkaPayloadResolver::resolve) + messages.map(::payload) + + multiFormatMessages.mapNotNull { message -> + nativeKafkaPayloadResolver.resolve(message) + ?.withHeaders(message.headers) + } private fun payload(msg: AnalyzedMessage): KafkaPayload { val type = resolvePayloadType(msg) @@ -93,6 +110,19 @@ class KotlinSpringKafkaModelFactory( } else { "$modelPackage.$type" }, + headerTypeName = msg.headers?.typeName, + headerImportName = msg.headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + msg.headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = headerName, + ) + } + .orEmpty(), ) } @@ -106,4 +136,21 @@ class KotlinSpringKafkaModelFactory( } private fun isPrimitive(type: String): Boolean = type in setOf("String", "Int", "Long", "Boolean", "java.math.BigDecimal") + + private fun KafkaPayload.withHeaders(headers: AnalyzedMessageHeaders?): KafkaPayload = + copy( + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = headerName, + ) + } + .orEmpty(), + ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt index 0cf98b75..a6b5e128 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt @@ -72,11 +72,23 @@ sealed interface GeneratorItem { val methodName: String, val payloadType: String, val keyType: String?, - ) + val headerType: String? = null, + ) { + val hasHeaders: Boolean get() = headerType != null + } data class SendMethod( val methodName: String, val payloadType: String, val keyType: String?, + val headerType: String? = null, + val headerProperties: List = emptyList(), + ) { + val hasHeaders: Boolean get() = headerType != null + } + + data class HeaderProperty( + val name: String, + val accessorName: String, ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/loader/HeaderSchemaCollector.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/loader/HeaderSchemaCollector.kt index 72724a85..7b07f1fc 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/loader/HeaderSchemaCollector.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/loader/HeaderSchemaCollector.kt @@ -1,16 +1,12 @@ package dev.banking.asyncapi.generator.core.generator.loader -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil -import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryType +import dev.banking.asyncapi.generator.core.generator.analyzer.MessageHeaderAnalyzer import dev.banking.asyncapi.generator.core.model.asyncapi.AsyncApiDocument import dev.banking.asyncapi.generator.core.model.channels.Channel import dev.banking.asyncapi.generator.core.model.channels.ChannelInterface import dev.banking.asyncapi.generator.core.model.messages.Message import dev.banking.asyncapi.generator.core.model.messages.MessageInterface -import dev.banking.asyncapi.generator.core.model.messages.MessageTrait -import dev.banking.asyncapi.generator.core.model.messages.MessageTraitInterface import dev.banking.asyncapi.generator.core.model.schemas.Schema -import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface object HeaderSchemaCollector { fun collect(asyncApiDocument: AsyncApiDocument): Map { @@ -28,49 +24,19 @@ object HeaderSchemaCollector { is MessageInterface.MessageInline -> messageInterface.message is MessageInterface.MessageReference -> messageInterface.reference.model as? Message } ?: return@forEach - val headers = collectHeaders(message) - if (headers.isEmpty()) return@forEach - val channelNamePascal = MapperUtil.toPascalCase(channelName) - val messageName = message.name ?: message.title ?: messageKey - val messageNamePascal = MapperUtil.toPascalCase(messageName) - val schemaName = "Topic${channelNamePascal}Headers$messageNamePascal" - headerSchemas[schemaName] = + val headers = + MessageHeaderAnalyzer.analyze( + channelName = channelName, + messageKey = messageKey, + message = message, + ) ?: return@forEach + headerSchemas[headers.typeName] = Schema( type = "object", - properties = headers, + properties = headers.properties, ) } } return headerSchemas } - - private fun collectHeaders(message: Message): Map { - val headers = mutableMapOf() - - message.traits?.forEach { traitInterface -> - val trait = - when (traitInterface) { - is MessageTraitInterface.InlineMessageTrait -> traitInterface.trait - is MessageTraitInterface.ReferenceMessageTrait -> traitInterface.reference.model as? MessageTrait - } - trait?.headers?.let { headers.putAll(extractHeaderProperties(it)) } - } - - message.headers?.let { headers.putAll(extractHeaderProperties(it)) } - - return headers - } - - private fun extractHeaderProperties(schemaInterface: SchemaInterface): Map = - when (schemaInterface) { - is SchemaInterface.SchemaInline -> { - val schema = schemaInterface.schema - if (schema.type.getPrimaryType() == "object") schema.properties ?: emptyMap() else emptyMap() - } - is SchemaInterface.SchemaReference -> { - val schema = schemaInterface.reference.model as? Schema - if (schema?.type.getPrimaryType() == "object") schema?.properties ?: emptyMap() else emptyMap() - } - else -> emptyMap() - } } diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache index 173621c0..99acd2e1 100644 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache +++ b/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache @@ -11,6 +11,6 @@ import {{.}}; */ public interface {{name}} { {{#methods}} - default void {{methodName}}(ConsumerRecord record) { } + default void {{methodName}}(ConsumerRecord record{{#hasHeaders}}, {{headerType}} headers{{/hasHeaders}}) { } {{/methods}} } diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache index acf76f6f..73deb488 100644 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache @@ -20,8 +20,19 @@ public class {{name}} { } {{#sendMethods}} - public void {{methodName}}(String key, {{payloadType}} message) { + public void {{methodName}}(String key, {{payloadType}} message{{#hasHeaders}}, {{headerType}} headers{{/hasHeaders}}) { +{{#hasHeaders}} + ProducerRecord record = new ProducerRecord<>(topic, key, message); +{{#headerProperties}} + if (headers != null && headers.{{accessorName}}() != null) { + record.headers().add("{{name}}", String.valueOf(headers.{{accessorName}}()).getBytes(StandardCharsets.UTF_8)); + } +{{/headerProperties}} + kafkaTemplate.send(record); +{{/hasHeaders}} +{{^hasHeaders}} kafkaTemplate.send(new ProducerRecord<>(topic, key, message)); +{{/hasHeaders}} } {{/sendMethods}} } diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache index 9a874524..a5a5a4fa 100644 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache +++ b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache @@ -11,6 +11,6 @@ import {{{.}}} */ interface {{name}} { {{#methods}} - fun {{methodName}}(record: ConsumerRecord) { } + fun {{methodName}}(record: ConsumerRecord{{#hasHeaders}}, headers: {{headerType}}{{/hasHeaders}}) { } {{/methods}} } diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache index 8ec1dd77..b13f06f3 100644 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache @@ -14,8 +14,17 @@ class {{name}}( private val topic: String ) { {{#sendMethods}} - fun {{methodName}}(key: String, message: {{payloadType}}) { + fun {{methodName}}(key: String, message: {{payloadType}}{{#hasHeaders}}, headers: {{headerType}}{{/hasHeaders}}) { +{{#hasHeaders}} + val record = ProducerRecord(topic, key, message) +{{#headerProperties}} + headers.{{accessorName}}?.let { record.headers().add("{{name}}", it.toString().toByteArray(Charsets.UTF_8)) } +{{/headerProperties}} + kafkaTemplate.send(record) +{{/hasHeaders}} +{{^hasHeaders}} kafkaTemplate.send(ProducerRecord(topic, key, message)) +{{/hasHeaders}} } {{/sendMethods}} } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzerTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzerTest.kt index 090dbff4..b5619c04 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzerTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/analyzer/ChannelAnalyzerTest.kt @@ -83,6 +83,40 @@ class ChannelAnalyzerTest { assertEquals(false, analyzed.isConsumer, "Should NOT be consumer") } + @Test + fun `should analyze generated header type for messages with headers`() { + val channel = Channel( + messages = mapOf( + "UserSignup" to MessageInterface.MessageInline( + Message( + name = "UserSignup", + headers = + SchemaInterface.SchemaInline( + Schema( + type = "object", + properties = + mapOf( + "correlationId" to SchemaInterface.SchemaInline(Schema(type = "string")), + ), + ), + ), + payload = SchemaInterface.SchemaInline(Schema(title = "MyPayload", type = "object")), + ), + ), + ), + ) + val doc = AsyncApiDocument( + asyncapi = "3.0.0", + info = Info("Title", "1.0"), + channels = mapOf("userEvents" to ChannelInterface.ChannelInline(channel)), + ) + + val analyzed = analyzer.analyze(doc).channels.single().messages.single() + + assertEquals("TopicUserEventsHeadersUserSignup", analyzed.headers?.typeName) + assertEquals(listOf("correlationId"), analyzed.headers?.properties?.keys?.toList()) + } + @Test fun `should preserve inline multi format payload separately from asyncapi messages`() { val avroSchema = nativeAvroSchema() diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt index 83a5ac00..ae09b066 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt @@ -57,8 +57,34 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { ) val outputDir = File("target/generated-sources/asyncapi") + val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") val headerDir = outputDir.resolve("dev/banking/test/userservice/v1/client/header") assertTrue(headerDir.exists(), "Spring Kafka client should generate header classes") + + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.java").readText() + assertTrue(consumerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup;")) + assertTrue( + consumerContent.contains( + "default void onUserSignup(ConsumerRecord record, " + + "TopicUserEventsHeadersUserSignup headers)", + ), + ) + + val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.java").readText() + assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup;")) + assertTrue(producerContent.contains("import java.nio.charset.StandardCharsets;")) + assertTrue( + producerContent.contains( + "public void sendUserSignup(String key, UserSignupPayload message, " + + "TopicUserEventsHeadersUserSignup headers)", + ), + ) + assertTrue( + producerContent.contains( + "record.headers().add(\"correlationId\", " + + "String.valueOf(headers.getCorrelationId()).getBytes(StandardCharsets.UTF_8));", + ), + ) } @Test diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt index 76ee1c00..476bca19 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt @@ -57,8 +57,32 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { ) val outputDir = File("target/generated-sources/asyncapi") + val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") val headerDir = outputDir.resolve("dev/banking/test/userservice/v1/client/header") assertTrue(headerDir.exists(), "Spring Kafka client should generate header classes") + + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + assertTrue(consumerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup")) + assertTrue( + consumerContent.contains( + "fun onUserSignup(record: ConsumerRecord, " + + "headers: TopicUserEventsHeadersUserSignup)", + ), + ) + + val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.kt").readText() + assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup")) + assertTrue( + producerContent.contains( + "fun sendUserSignup(key: String, message: UserSignupPayload, headers: TopicUserEventsHeadersUserSignup)", + ), + ) + assertTrue( + producerContent.contains( + "headers.correlationId?.let { record.headers().add(\"correlationId\", " + + "it.toString().toByteArray(Charsets.UTF_8)) }", + ), + ) } @Test From 3ee4a66210eb225d04b87b40f98e890385a826b8 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:06:40 +0200 Subject: [PATCH 05/10] kafka header generation setting in spring-kafka clients --- .../factory/JavaSpringKafkaModelFactory.kt | 42 +++++++++++-------- .../kafka/spring/JavaSpringKafkaGenerator.kt | 2 + .../spring/SpringKafkaClientGeneration.kt | 2 + .../factory/KotlinSpringKafkaModelFactory.kt | 42 +++++++++++-------- .../spring/KotlinSpringKafkaGenerator.kt | 2 + .../core/generator/plan/GenerationPlanner.kt | 1 + .../core/generator/plan/GenerationTask.kt | 1 + .../generator/AbstractJavaGeneratorClass.kt | 2 + .../generator/AbstractKotlinGeneratorClass.kt | 2 + .../java/kafka/GenerateJavaSpringKafkaTest.kt | 39 +++++++++++++++++ .../spring/SpringKafkaClientGenerationTest.kt | 1 + .../kafka/GenerateKotlinSpringKafkaTest.kt | 38 +++++++++++++++++ .../generator/plan/GenerationPlannerTest.kt | 6 ++- 13 files changed, 143 insertions(+), 37 deletions(-) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index 6c5dd67b..40351e59 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -16,6 +16,7 @@ import dev.banking.asyncapi.generator.core.model.schemas.SchemaInterface class JavaSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, + private val generateHeaders: Boolean = true, private val generateProducers: Boolean = true, private val generateConsumers: Boolean = true, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), @@ -106,6 +107,7 @@ class JavaSpringKafkaModelFactory( private fun payload(msg: AnalyzedMessage): KafkaPayload { val type = resolvePayloadType(msg) + val headers = if (generateHeaders) msg.headers else null return KafkaPayload( messageName = msg.messageName, payloadType = type, @@ -115,10 +117,10 @@ class JavaSpringKafkaModelFactory( } else { "$modelPackage.$type" }, - headerTypeName = msg.headers?.typeName, - headerImportName = msg.headers?.typeName?.let { "$clientPackage.header.$it" }, + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, headerProperties = - msg.headers + headers ?.properties ?.keys ?.map { headerName -> @@ -170,21 +172,25 @@ class JavaSpringKafkaModelFactory( type in setOf("String", "Integer", "Long", "Boolean", "Double", "java.math.BigDecimal", "Object") private fun KafkaPayload.withHeaders(headers: AnalyzedMessageHeaders?): KafkaPayload = - copy( - headerTypeName = headers?.typeName, - headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, - headerProperties = - headers - ?.properties - ?.keys - ?.map { headerName -> - KafkaHeaderProperty( - name = headerName, - accessorName = getterName(headerName), - ) - } - .orEmpty(), - ) + if (generateHeaders) { + copy( + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = getterName(headerName), + ) + } + .orEmpty(), + ) + } else { + this + } private fun getterName(propertyName: String): String = "get" + propertyName.replaceFirstChar { it.uppercase() } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt index fa395027..ab466e76 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/spring/JavaSpringKafkaGenerator.kt @@ -9,6 +9,7 @@ class JavaSpringKafkaGenerator( outputDir: File, clientPackage: String, modelPackage: String, + generateHeaders: Boolean = true, generateProducers: Boolean = true, generateConsumers: Boolean = true, ) { @@ -16,6 +17,7 @@ class JavaSpringKafkaGenerator( JavaSpringKafkaModelFactory( clientPackage = clientPackage, modelPackage = modelPackage, + generateHeaders = generateHeaders, generateProducers = generateProducers, generateConsumers = generateConsumers, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt index ec4af5b8..c10066c8 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGeneration.kt @@ -38,6 +38,7 @@ class SpringKafkaClientGeneration { outputDir = sourceOutputDirectory, clientPackage = task.clientPackage, modelPackage = task.modelPackage, + generateHeaders = task.generateHeaders, generateProducers = task.generateProducers, generateConsumers = task.generateConsumers, ) @@ -54,6 +55,7 @@ class SpringKafkaClientGeneration { outputDir = sourceOutputDirectory, clientPackage = task.clientPackage, modelPackage = task.modelPackage, + generateHeaders = task.generateHeaders, generateProducers = task.generateProducers, generateConsumers = task.generateConsumers, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index b5ac697a..4c84b645 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -14,6 +14,7 @@ import dev.banking.asyncapi.generator.core.generator.util.MapperUtil.getPrimaryT class KotlinSpringKafkaModelFactory( private val clientPackage: String, private val modelPackage: String, + private val generateHeaders: Boolean = true, private val generateProducers: Boolean = true, private val generateConsumers: Boolean = true, private val nativeKafkaPayloadResolver: NativeKafkaPayloadResolver = NativeKafkaPayloadResolver(), @@ -101,6 +102,7 @@ class KotlinSpringKafkaModelFactory( private fun payload(msg: AnalyzedMessage): KafkaPayload { val type = resolvePayloadType(msg) + val headers = if (generateHeaders) msg.headers else null return KafkaPayload( messageName = msg.messageName, payloadType = type, @@ -110,10 +112,10 @@ class KotlinSpringKafkaModelFactory( } else { "$modelPackage.$type" }, - headerTypeName = msg.headers?.typeName, - headerImportName = msg.headers?.typeName?.let { "$clientPackage.header.$it" }, + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, headerProperties = - msg.headers + headers ?.properties ?.keys ?.map { headerName -> @@ -138,19 +140,23 @@ class KotlinSpringKafkaModelFactory( private fun isPrimitive(type: String): Boolean = type in setOf("String", "Int", "Long", "Boolean", "java.math.BigDecimal") private fun KafkaPayload.withHeaders(headers: AnalyzedMessageHeaders?): KafkaPayload = - copy( - headerTypeName = headers?.typeName, - headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, - headerProperties = - headers - ?.properties - ?.keys - ?.map { headerName -> - KafkaHeaderProperty( - name = headerName, - accessorName = headerName, - ) - } - .orEmpty(), - ) + if (generateHeaders) { + copy( + headerTypeName = headers?.typeName, + headerImportName = headers?.typeName?.let { "$clientPackage.header.$it" }, + headerProperties = + headers + ?.properties + ?.keys + ?.map { headerName -> + KafkaHeaderProperty( + name = headerName, + accessorName = headerName, + ) + } + .orEmpty(), + ) + } else { + this + } } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt index 328ea728..5f8ff711 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/spring/KotlinSpringKafkaGenerator.kt @@ -9,6 +9,7 @@ class KotlinSpringKafkaGenerator( outputDir: File, clientPackage: String, modelPackage: String, + generateHeaders: Boolean = true, generateProducers: Boolean = true, generateConsumers: Boolean = true, ) { @@ -16,6 +17,7 @@ class KotlinSpringKafkaGenerator( KotlinSpringKafkaModelFactory( clientPackage = clientPackage, modelPackage = modelPackage, + generateHeaders = generateHeaders, generateProducers = generateProducers, generateConsumers = generateConsumers, ) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt index 6560f791..8995c952 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt @@ -45,6 +45,7 @@ class GenerationPlanner { language = configuration.language, clientPackage = client.packageName, modelPackage = client.modelPackageName, + generateHeaders = client.headers.enabled, generateProducers = springKafka.producer.enabled, generateConsumers = springKafka.consumer.enabled, ), diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt index da75930a..d0b4ec6d 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationTask.kt @@ -26,6 +26,7 @@ sealed interface GenerationTask { val language: GeneratorName, val clientPackage: String, val modelPackage: String, + val generateHeaders: Boolean = true, val generateProducers: Boolean = true, val generateConsumers: Boolean = true, ) : GenerationTask diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt index 1a4d9deb..ec5eab1c 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractJavaGeneratorClass.kt @@ -25,6 +25,7 @@ abstract class AbstractJavaGeneratorClass { schemaPackage: String? = null, generateModels: Boolean = true, generateSpringKafkaClient: Boolean = false, + generateKafkaHeaders: Boolean = true, generateQuarkusKafkaClient: Boolean = false, modelAnnotation: String? = null, javaModelType: JavaModelType = JavaModelType.CLASS, @@ -56,6 +57,7 @@ abstract class AbstractJavaGeneratorClass { ClientGeneration.Kafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, + headers = ClientGeneration.Headers(enabled = generateKafkaHeaders), springKafka = ClientGeneration.SpringKafka(), ), ) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt index c9df0383..1fe02c9c 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/AbstractKotlinGeneratorClass.kt @@ -24,6 +24,7 @@ abstract class AbstractKotlinGeneratorClass { schemaPackage: String? = null, generateModels: Boolean = true, generateSpringKafkaClient: Boolean = false, + generateKafkaHeaders: Boolean = true, generateQuarkusKafkaClient: Boolean = false, modelAnnotation: String? = null, ): String { @@ -53,6 +54,7 @@ abstract class AbstractKotlinGeneratorClass { ClientGeneration.Kafka( packageName = effectiveClientPackage, modelPackageName = modelPackage, + headers = ClientGeneration.Headers(enabled = generateKafkaHeaders), springKafka = ClientGeneration.SpringKafka(), ), ) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt index ae09b066..fe0d699f 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt @@ -3,6 +3,7 @@ package dev.banking.asyncapi.generator.core.generator.java.kafka import dev.banking.asyncapi.generator.core.generator.AbstractJavaGeneratorClass import org.junit.jupiter.api.Test import java.io.File +import kotlin.test.assertFalse import kotlin.test.assertTrue class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { @@ -87,6 +88,44 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { ) } + @Test + fun `should not reference typed headers when Kafka header generation is disabled for Java`() { + val yaml = File("src/test/resources/generator/asyncapi_message_headers.yaml") + val modelPackage = "dev.banking.test.userservice.v1.model" + val clientPackage = "dev.banking.test.userservice.v1.client" + val outputDir = File("target/generated-sources/asyncapi-java-spring-kafka-no-headers") + val resourceOutputDirectory = File("target/generated-resources/asyncapi-java-spring-kafka-no-headers") + outputDir.deleteRecursively() + resourceOutputDirectory.deleteRecursively() + + generateElement( + yaml = yaml, + codegenOutputDirectory = outputDir, + resourceOutputDirectory = resourceOutputDirectory, + modelPackage = modelPackage, + clientPackage = clientPackage, + generateModels = true, + generateSpringKafkaClient = true, + generateKafkaHeaders = false, + ) + + val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") + val headerDir = clientDir.resolve("header") + assertFalse(headerDir.exists(), "Header classes should not be generated when Kafka headers are disabled") + + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.java").readText() + assertFalse(consumerContent.contains(".client.header.")) + assertFalse(consumerContent.contains("TopicUserEventsHeadersUserSignup")) + assertTrue(consumerContent.contains("default void onUserSignup(ConsumerRecord record)")) + + val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.java").readText() + assertFalse(producerContent.contains(".client.header.")) + assertFalse(producerContent.contains("TopicUserEventsHeadersUserSignup")) + assertFalse(producerContent.contains("StandardCharsets")) + assertFalse(producerContent.contains("record.headers().add")) + assertTrue(producerContent.contains("public void sendUserSignup(String key, UserSignupPayload message)")) + } + @Test fun `should generate spring kafka client with native avro payload type for Java`() { val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt index e7d4c620..e315ee64 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kafka/spring/SpringKafkaClientGenerationTest.kt @@ -96,6 +96,7 @@ class SpringKafkaClientGenerationTest { language = language, clientPackage = "com.example.client", modelPackage = "com.example.model", + generateHeaders = true, generateProducers = generateProducers, generateConsumers = generateConsumers, ) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt index 476bca19..c2fb1883 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt @@ -3,6 +3,7 @@ package dev.banking.asyncapi.generator.core.generator.kotlin.kafka import dev.banking.asyncapi.generator.core.generator.AbstractKotlinGeneratorClass import org.junit.jupiter.api.Test import java.io.File +import kotlin.test.assertFalse import kotlin.test.assertTrue class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { @@ -85,6 +86,43 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { ) } + @Test + fun `should not reference typed headers when Kafka header generation is disabled`() { + val yaml = File("src/test/resources/generator/asyncapi_message_headers.yaml") + val modelPackage = "dev.banking.test.userservice.v1.model" + val clientPackage = "dev.banking.test.userservice.v1.client" + val outputDir = File("target/generated-sources/asyncapi-kotlin-spring-kafka-no-headers") + val resourceOutputDirectory = File("target/generated-resources/asyncapi-kotlin-spring-kafka-no-headers") + outputDir.deleteRecursively() + resourceOutputDirectory.deleteRecursively() + + generateElement( + yaml = yaml, + codegenOutputDirectory = outputDir, + resourceOutputDirectory = resourceOutputDirectory, + modelPackage = modelPackage, + clientPackage = clientPackage, + generateModels = true, + generateSpringKafkaClient = true, + generateKafkaHeaders = false, + ) + + val clientDir = outputDir.resolve("dev/banking/test/userservice/v1/client") + val headerDir = clientDir.resolve("header") + assertFalse(headerDir.exists(), "Header classes should not be generated when Kafka headers are disabled") + + val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.kt").readText() + assertFalse(consumerContent.contains(".client.header.")) + assertFalse(consumerContent.contains("TopicUserEventsHeadersUserSignup")) + assertTrue(consumerContent.contains("fun onUserSignup(record: ConsumerRecord)")) + + val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.kt").readText() + assertFalse(producerContent.contains(".client.header.")) + assertFalse(producerContent.contains("TopicUserEventsHeadersUserSignup")) + assertFalse(producerContent.contains("record.headers().add")) + assertTrue(producerContent.contains("fun sendUserSignup(key: String, message: UserSignupPayload)")) + } + @Test fun `should generate spring kafka client with native avro payload type`() { val yaml = File("src/test/resources/generator/asyncapi_native_avro_spring_kafka_client.yaml") diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt index 060091d2..813b2231 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt @@ -164,7 +164,9 @@ class GenerationPlannerTest { assertEquals( listOf( - springKafkaClientTask(), + springKafkaClientTask( + generateHeaders = false, + ), ), plan.tasks, ) @@ -277,6 +279,7 @@ class GenerationPlannerTest { language: GeneratorName = GeneratorName.KOTLIN, clientPackage: String = "com.example.client", modelPackage: String = "com.example.model", + generateHeaders: Boolean = true, generateProducers: Boolean = true, generateConsumers: Boolean = true, ): GenerationTask.SpringKafkaClient = @@ -284,6 +287,7 @@ class GenerationPlannerTest { language = language, clientPackage = clientPackage, modelPackage = modelPackage, + generateHeaders = generateHeaders, generateProducers = generateProducers, generateConsumers = generateConsumers, ) From 049d4b4e9dd34ac53271e2bebc28595fc02d7ff6 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:17:06 +0200 Subject: [PATCH 06/10] return spring-kafka send futures from generated producers --- README.md | 2 +- .../factory/JavaSpringKafkaModelFactory.kt | 2 ++ .../factory/KotlinSpringKafkaModelFactory.kt | 8 ++++++- .../java/spring-kafka-producer.mustache | 10 ++++++--- .../kotlin/spring-kafka-producer.mustache | 10 ++++++--- .../kafka/GenerateJavaPrimitivePayloadTest.kt | 12 ++++++++++ ...ateJavaSpringKafkaOpenPayloadClientTest.kt | 2 +- .../java/kafka/GenerateJavaSpringKafkaTest.kt | 20 +++++++++++------ ...eKotlinSpringKafkaOpenPayloadClientTest.kt | 2 ++ .../kafka/GenerateKotlinSpringKafkaTest.kt | 22 ++++++++++++++----- .../kafka/GeneratePrimitivePayloadTest.kt | 12 ++++++++++ .../test/it/spring-kafka-simple/verify.groovy | 1 + 12 files changed, 81 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 44515f4e..73a3d273 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ For native Protobuf message payloads, generated Spring Kafka clients use the Jav The generator does not configure Kafka Avro or Protobuf serializers and deserializers yet; applications still own that runtime wiring. -Generated Spring Kafka clients are contract-only source artifacts. Producer-oriented channels generate producer wrappers around application-provided `KafkaTemplate` instances. Consumer-oriented channels generate consumer interfaces that receive typed `ConsumerRecord` values. The generator does not create Spring Boot auto-configuration, `@KafkaListener` classes, listener containers, serializer configuration, deserializer configuration, or schema registry configuration. +Generated Spring Kafka clients are contract-only source artifacts. Producer-oriented channels generate producer wrappers around application-provided `KafkaTemplate` instances and return Spring Kafka `CompletableFuture>` values from send methods. Consumer-oriented channels generate consumer interfaces that receive typed `ConsumerRecord` values. The generator does not create Spring Boot auto-configuration, `@KafkaListener` classes, listener containers, serializer configuration, deserializer configuration, or schema registry configuration. The generated output depends on the channel direction from the AsyncAPI operations. Producer-oriented channels generate producer artifacts. Consumer-oriented channels generate consumer artifacts. When the channel direction is not declared, the generator treats the channel as both producer and consumer. diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index 40351e59..10186097 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -59,8 +59,10 @@ class JavaSpringKafkaModelFactory( val imports = ( baseImports + + "java.util.concurrent.CompletableFuture" + "org.apache.kafka.clients.producer.ProducerRecord" + "org.springframework.kafka.core.KafkaTemplate" + + "org.springframework.kafka.support.SendResult" + listOfNotNull("java.nio.charset.StandardCharsets".takeIf { payloads.any { it.headerProperties.isNotEmpty() } }) ) .distinct() diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index 4c84b645..9604141b 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -56,7 +56,13 @@ class KotlinSpringKafkaModelFactory( if (channel.isProducer && generateProducers) { val imports = - (baseImports + "org.apache.kafka.clients.producer.ProducerRecord" + "org.springframework.kafka.core.KafkaTemplate") + ( + baseImports + + "java.util.concurrent.CompletableFuture" + + "org.apache.kafka.clients.producer.ProducerRecord" + + "org.springframework.kafka.core.KafkaTemplate" + + "org.springframework.kafka.support.SendResult" + ) .distinct() .sorted() payloads.forEach { payload -> diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache index 73deb488..5c026d00 100644 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/java/spring-kafka-producer.mustache @@ -20,7 +20,11 @@ public class {{name}} { } {{#sendMethods}} - public void {{methodName}}(String key, {{payloadType}} message{{#hasHeaders}}, {{headerType}} headers{{/hasHeaders}}) { + public CompletableFuture> {{methodName}}( + String key, + {{payloadType}} message{{#hasHeaders}}, + {{headerType}} headers{{/hasHeaders}} + ) { {{#hasHeaders}} ProducerRecord record = new ProducerRecord<>(topic, key, message); {{#headerProperties}} @@ -28,10 +32,10 @@ public class {{name}} { record.headers().add("{{name}}", String.valueOf(headers.{{accessorName}}()).getBytes(StandardCharsets.UTF_8)); } {{/headerProperties}} - kafkaTemplate.send(record); + return kafkaTemplate.send(record); {{/hasHeaders}} {{^hasHeaders}} - kafkaTemplate.send(new ProducerRecord<>(topic, key, message)); + return kafkaTemplate.send(new ProducerRecord<>(topic, key, message)); {{/hasHeaders}} } {{/sendMethods}} diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache index b13f06f3..65ef424a 100644 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache +++ b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-producer.mustache @@ -14,16 +14,20 @@ class {{name}}( private val topic: String ) { {{#sendMethods}} - fun {{methodName}}(key: String, message: {{payloadType}}{{#hasHeaders}}, headers: {{headerType}}{{/hasHeaders}}) { + fun {{methodName}}( + key: String, + message: {{payloadType}}{{#hasHeaders}}, + headers: {{headerType}}{{/hasHeaders}} + ): CompletableFuture> { {{#hasHeaders}} val record = ProducerRecord(topic, key, message) {{#headerProperties}} headers.{{accessorName}}?.let { record.headers().add("{{name}}", it.toString().toByteArray(Charsets.UTF_8)) } {{/headerProperties}} - kafkaTemplate.send(record) + return kafkaTemplate.send(record) {{/hasHeaders}} {{^hasHeaders}} - kafkaTemplate.send(ProducerRecord(topic, key, message)) + return kafkaTemplate.send(ProducerRecord(topic, key, message)) {{/hasHeaders}} } {{/sendMethods}} diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt index e7b514cf..e5e01d76 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaPrimitivePayloadTest.kt @@ -50,6 +50,10 @@ class GenerateJavaPrimitivePayloadTest { producerContent.contains("KafkaTemplate"), "Producer should use typed KafkaTemplate for single payload", ) + assertTrue( + producerContent.contains("CompletableFuture>"), + "Producer should return the Spring Kafka send result future", + ) } @Test @@ -97,9 +101,17 @@ class GenerateJavaPrimitivePayloadTest { producerContentA.contains("KafkaTemplate"), "StringMessage producer should use typed KafkaTemplate", ) + assertTrue( + producerContentA.contains("CompletableFuture>"), + "StringMessage producer should return the Spring Kafka send result future", + ) assertTrue( producerContentB.contains("KafkaTemplate"), "IntMessage producer should use typed KafkaTemplate", ) + assertTrue( + producerContentB.contains("CompletableFuture>"), + "IntMessage producer should return the Spring Kafka send result future", + ) } } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt index 8c9f4847..c17d0db3 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaOpenPayloadClientTest.kt @@ -32,7 +32,7 @@ class GenerateJavaSpringKafkaOpenPayloadClientTest : AbstractJavaGeneratorClass( val producerContent = producerDir.resolve("UserDlqProducerDeadLetterQueueEvent.java").readText() assertTrue(producerContent.contains("KafkaTemplate")) - assertTrue(producerContent.contains("void sendDeadLetterQueueEvent")) + assertTrue(producerContent.contains("CompletableFuture> sendDeadLetterQueueEvent")) val consumerContent = consumerDir.resolve("UserDlqConsumer.java").readText() assertTrue(consumerContent.contains("ConsumerRecord")) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt index fe0d699f..ad37d706 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt @@ -32,6 +32,8 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(producerContent.contains("class UserEventsProducerUserSignedUp")) assertTrue(producerContent.contains("KafkaTemplate")) assertTrue(producerContent.contains("sendUserSignedUp")) + assertTrue(producerContent.contains("CompletableFuture>")) + assertTrue(producerContent.contains("return kafkaTemplate.send")) assertTrue(!producerContent.contains("@Component"), "Producer should not be annotated") val consumerFile = consumerDir.resolve("UserEventsConsumer.java") @@ -73,13 +75,12 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.java").readText() assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup;")) + assertTrue(producerContent.contains("import java.util.concurrent.CompletableFuture;")) + assertTrue(producerContent.contains("import org.springframework.kafka.support.SendResult;")) assertTrue(producerContent.contains("import java.nio.charset.StandardCharsets;")) - assertTrue( - producerContent.contains( - "public void sendUserSignup(String key, UserSignupPayload message, " + - "TopicUserEventsHeadersUserSignup headers)", - ), - ) + assertTrue(producerContent.contains("CompletableFuture> sendUserSignup(")) + assertTrue(producerContent.contains("UserSignupPayload message")) + assertTrue(producerContent.contains("TopicUserEventsHeadersUserSignup headers")) assertTrue( producerContent.contains( "record.headers().add(\"correlationId\", " + @@ -123,7 +124,8 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertFalse(producerContent.contains("TopicUserEventsHeadersUserSignup")) assertFalse(producerContent.contains("StandardCharsets")) assertFalse(producerContent.contains("record.headers().add")) - assertTrue(producerContent.contains("public void sendUserSignup(String key, UserSignupPayload message)")) + assertTrue(producerContent.contains("CompletableFuture> sendUserSignup(")) + assertTrue(producerContent.contains("UserSignupPayload message")) } @Test @@ -149,6 +151,7 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.avro.UserCreated;")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -174,6 +177,7 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.avro.UserCreatedAvro;")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -199,6 +203,7 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.protobuf.UserCreated;")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -224,5 +229,6 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf;")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt index 22d18fd2..e15ec660 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOpenPayloadClientTest.kt @@ -31,6 +31,7 @@ class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorCl val producerContent = producerDir.resolve("UserDlqProducerDeadLetterQueueEvent.kt").readText() assertTrue(producerContent.contains("KafkaTemplate")) assertTrue(producerContent.contains("fun sendDeadLetterQueueEvent")) + assertTrue(producerContent.contains("CompletableFuture>")) assertTrue(producerContent.contains("import $modelPackage.DeadLetterQueueEvent")) val consumerContent = consumerDir.resolve("UserDlqConsumer.kt").readText() @@ -64,6 +65,7 @@ class GenerateKotlinSpringKafkaOpenPayloadClientTest : AbstractKotlinGeneratorCl val producerContent = producerDir.resolve("UserDlqProducerDeadLetterQueueEvent.kt").readText() assertTrue(producerContent.contains("KafkaTemplate")) assertTrue(producerContent.contains("fun sendDeadLetterQueueEvent")) + assertTrue(producerContent.contains("CompletableFuture>")) assertTrue(producerContent.contains("import $modelPackage.DeadLetterQueueEventPayload")) val consumerContent = consumerDir.resolve("UserDlqConsumer.kt").readText() diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt index c2fb1883..3dd53d79 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt @@ -32,6 +32,8 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(producerContent.contains("class UserEventsProducerUserSignedUp")) assertTrue(producerContent.contains("KafkaTemplate")) assertTrue(producerContent.contains("sendUserSignedUp")) + assertTrue(producerContent.contains("CompletableFuture>")) + assertTrue(producerContent.contains("return kafkaTemplate.send")) assertTrue(!producerContent.contains("@Component"), "Producer should not be annotated") val consumerFile = consumerDir.resolve("UserEventsConsumer.kt") @@ -73,11 +75,12 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.kt").readText() assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup")) - assertTrue( - producerContent.contains( - "fun sendUserSignup(key: String, message: UserSignupPayload, headers: TopicUserEventsHeadersUserSignup)", - ), - ) + assertTrue(producerContent.contains("import java.util.concurrent.CompletableFuture")) + assertTrue(producerContent.contains("import org.springframework.kafka.support.SendResult")) + assertTrue(producerContent.contains("fun sendUserSignup(")) + assertTrue(producerContent.contains("message: UserSignupPayload")) + assertTrue(producerContent.contains("headers: TopicUserEventsHeadersUserSignup")) + assertTrue(producerContent.contains("CompletableFuture>")) assertTrue( producerContent.contains( "headers.correlationId?.let { record.headers().add(\"correlationId\", " + @@ -120,7 +123,10 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertFalse(producerContent.contains(".client.header.")) assertFalse(producerContent.contains("TopicUserEventsHeadersUserSignup")) assertFalse(producerContent.contains("record.headers().add")) - assertTrue(producerContent.contains("fun sendUserSignup(key: String, message: UserSignupPayload)")) + assertFalse(producerContent.contains("headers:")) + assertTrue(producerContent.contains("fun sendUserSignup(")) + assertTrue(producerContent.contains("message: UserSignupPayload")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -146,6 +152,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.avro.UserCreated")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -171,6 +178,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.avro.UserCreatedAvro")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -196,6 +204,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.protobuf.UserCreated")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } @Test @@ -221,5 +230,6 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(producerContent.contains("import com.example.external.protobuf.UserCreatedProtobuf")) assertTrue(producerContent.contains("KafkaTemplate")) + assertTrue(producerContent.contains("CompletableFuture>")) } } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt index 6ba638ea..efe73add 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GeneratePrimitivePayloadTest.kt @@ -59,6 +59,10 @@ class GeneratePrimitivePayloadTest : AbstractKotlinGeneratorClass() { producerContent.contains("KafkaTemplate"), "Producer should use typed KafkaTemplate for single payload", ) + assertTrue( + producerContent.contains("CompletableFuture>"), + "Producer should return the Spring Kafka send result future", + ) } @Test @@ -106,9 +110,17 @@ class GeneratePrimitivePayloadTest : AbstractKotlinGeneratorClass() { producerContentA.contains("KafkaTemplate"), "StringMessage producer should use typed KafkaTemplate", ) + assertTrue( + producerContentA.contains("CompletableFuture>"), + "StringMessage producer should return the Spring Kafka send result future", + ) assertTrue( producerContentB.contains("KafkaTemplate"), "IntMessage producer should use typed KafkaTemplate", ) + assertTrue( + producerContentB.contains("CompletableFuture>"), + "IntMessage producer should return the Spring Kafka send result future", + ) } } diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy index 9872783c..be6b7a22 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy @@ -4,6 +4,7 @@ assert producer.exists() : "Expected UserEventsProducerUserSignedUp.kt to be gen def producerContent = producer.text assert producerContent.contains("class UserEventsProducerUserSignedUp") : "Expected producer class name" assert producerContent.contains("KafkaTemplate") : "Expected typed KafkaTemplate" +assert producerContent.contains("CompletableFuture>") : "Expected producer send future return type" assert !producerContent.contains("@Component") : "Simple producer should not be annotated" def consumer = new File(basedir, "target/generated-sources/asyncapi/com/example/simple/client/consumer/UserEventsConsumer.kt") From 11878acfe561002b4d86ac180684aee14a82a91f Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:26:07 +0200 Subject: [PATCH 07/10] make generated spring-kafka consumer methods abstract --- README.md | 2 +- .../resources/java/spring-kafka-consumer.mustache | 2 +- .../resources/kotlin/spring-kafka-consumer.mustache | 2 +- .../java/kafka/GenerateJavaSpringKafkaTest.kt | 11 +++++++---- .../kotlin/kafka/GenerateKotlinSpringKafkaTest.kt | 3 +++ .../src/test/it/spring-kafka-simple/verify.groovy | 1 + 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 73a3d273..2bba41ed 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ For native Protobuf message payloads, generated Spring Kafka clients use the Jav The generator does not configure Kafka Avro or Protobuf serializers and deserializers yet; applications still own that runtime wiring. -Generated Spring Kafka clients are contract-only source artifacts. Producer-oriented channels generate producer wrappers around application-provided `KafkaTemplate` instances and return Spring Kafka `CompletableFuture>` values from send methods. Consumer-oriented channels generate consumer interfaces that receive typed `ConsumerRecord` values. The generator does not create Spring Boot auto-configuration, `@KafkaListener` classes, listener containers, serializer configuration, deserializer configuration, or schema registry configuration. +Generated Spring Kafka clients are contract-only source artifacts. Producer-oriented channels generate producer wrappers around application-provided `KafkaTemplate` instances and return Spring Kafka `CompletableFuture>` values from send methods. Consumer-oriented channels generate consumer interfaces with abstract methods that receive typed `ConsumerRecord` values. The generator does not create Spring Boot auto-configuration, `@KafkaListener` classes, listener containers, serializer configuration, deserializer configuration, or schema registry configuration. The generated output depends on the channel direction from the AsyncAPI operations. Producer-oriented channels generate producer artifacts. Consumer-oriented channels generate consumer artifacts. When the channel direction is not declared, the generator treats the channel as both producer and consumer. diff --git a/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache b/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache index 99acd2e1..ab3db0bd 100644 --- a/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache +++ b/asyncapi-generator-core/src/main/resources/java/spring-kafka-consumer.mustache @@ -11,6 +11,6 @@ import {{.}}; */ public interface {{name}} { {{#methods}} - default void {{methodName}}(ConsumerRecord record{{#hasHeaders}}, {{headerType}} headers{{/hasHeaders}}) { } + void {{methodName}}(ConsumerRecord record{{#hasHeaders}}, {{headerType}} headers{{/hasHeaders}}); {{/methods}} } diff --git a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache index a5a5a4fa..725f875f 100644 --- a/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache +++ b/asyncapi-generator-core/src/main/resources/kotlin/spring-kafka-consumer.mustache @@ -11,6 +11,6 @@ import {{{.}}} */ interface {{name}} { {{#methods}} - fun {{methodName}}(record: ConsumerRecord{{#hasHeaders}}, headers: {{headerType}}{{/hasHeaders}}) { } + fun {{methodName}}(record: ConsumerRecord{{#hasHeaders}}, headers: {{headerType}}{{/hasHeaders}}) {{/methods}} } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt index ad37d706..c858ebd2 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/java/kafka/GenerateJavaSpringKafkaTest.kt @@ -40,7 +40,8 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerFile.exists(), "Consumer should be generated") val consumerContent = consumerFile.readText() assertTrue(consumerContent.contains("interface UserEventsConsumer")) - assertTrue(consumerContent.contains("default void onUserSignedUp")) + assertTrue(consumerContent.contains("void onUserSignedUp")) + assertFalse(consumerContent.contains("default void"), "Consumer methods should be abstract") assertTrue(consumerContent.contains("ConsumerRecord")) assertTrue(!consumerContent.contains("@KafkaListener"), "Consumer should not be annotated") } @@ -68,10 +69,11 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { assertTrue(consumerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup;")) assertTrue( consumerContent.contains( - "default void onUserSignup(ConsumerRecord record, " + - "TopicUserEventsHeadersUserSignup headers)", + "void onUserSignup(ConsumerRecord record, " + + "TopicUserEventsHeadersUserSignup headers);", ), ) + assertFalse(consumerContent.contains("default void"), "Consumer methods should be abstract") val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.java").readText() assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup;")) @@ -117,7 +119,8 @@ class GenerateJavaSpringKafkaTest : AbstractJavaGeneratorClass() { val consumerContent = clientDir.resolve("consumer/UserEventsConsumer.java").readText() assertFalse(consumerContent.contains(".client.header.")) assertFalse(consumerContent.contains("TopicUserEventsHeadersUserSignup")) - assertTrue(consumerContent.contains("default void onUserSignup(ConsumerRecord record)")) + assertTrue(consumerContent.contains("void onUserSignup(ConsumerRecord record);")) + assertFalse(consumerContent.contains("default void"), "Consumer methods should be abstract") val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.java").readText() assertFalse(producerContent.contains(".client.header.")) diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt index 3dd53d79..092486ea 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaTest.kt @@ -42,6 +42,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertTrue(consumerContent.contains("interface UserEventsConsumer")) assertTrue(consumerContent.contains("fun onUserSignedUp")) assertTrue(consumerContent.contains("ConsumerRecord")) + assertFalse(consumerContent.contains("{ }"), "Consumer methods should be abstract") assertTrue(!consumerContent.contains("@KafkaListener"), "Consumer should not be annotated") } @@ -72,6 +73,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { "headers: TopicUserEventsHeadersUserSignup)", ), ) + assertFalse(consumerContent.contains("{ }"), "Consumer methods should be abstract") val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.kt").readText() assertTrue(producerContent.contains("import dev.banking.test.userservice.v1.client.header.TopicUserEventsHeadersUserSignup")) @@ -118,6 +120,7 @@ class GenerateKotlinSpringKafkaTest : AbstractKotlinGeneratorClass() { assertFalse(consumerContent.contains(".client.header.")) assertFalse(consumerContent.contains("TopicUserEventsHeadersUserSignup")) assertTrue(consumerContent.contains("fun onUserSignup(record: ConsumerRecord)")) + assertFalse(consumerContent.contains("{ }"), "Consumer methods should be abstract") val producerContent = clientDir.resolve("producer/UserEventsProducerUserSignup.kt").readText() assertFalse(producerContent.contains(".client.header.")) diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy index be6b7a22..cf7fb7f9 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy @@ -13,4 +13,5 @@ assert consumer.exists() : "Expected UserEventsConsumer.kt to be generated" def consumerContent = consumer.text assert consumerContent.contains("interface UserEventsConsumer") : "Expected consumer interface" assert consumerContent.contains("fun onUserSignedUp") : "Expected onUserSignedUp method" +assert !consumerContent.contains("{ }") : "Expected abstract consumer method" assert !consumerContent.contains("@KafkaListener") : "Simple consumer should not be annotated" From 66ab47676fb8b5e0dc4b193045c67edc79d60bd5 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:29:29 +0200 Subject: [PATCH 08/10] remove unused spring-kafka listener model --- .../java/factory/JavaSpringKafkaModelFactory.kt | 1 - .../core/generator/java/model/GeneratorItem.kt | 14 -------------- .../factory/KotlinSpringKafkaModelFactory.kt | 1 - .../core/generator/kotlin/model/GeneratorItem.kt | 14 -------------- .../GenerateKotlinSpringKafkaOperationsTest.kt | 10 +++++----- 5 files changed, 5 insertions(+), 35 deletions(-) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt index 10186097..bdd0edab 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/factory/JavaSpringKafkaModelFactory.kt @@ -91,7 +91,6 @@ class JavaSpringKafkaModelFactory( sendMethods = listOf(sendMethod), kafkaValueType = payload.payloadType, imports = imports, - topicPropertyKey = "", ), ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt index e7861b3f..3aededb2 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/java/model/GeneratorItem.kt @@ -41,19 +41,6 @@ sealed interface GeneratorItem { val imports: List = emptyList(), ) : GeneratorItem - data class KafkaListenerClass( - override val name: String, - override val packageName: String, - override val description: List, - val topic: String, - val groupId: String, - val handlerInterface: String, - val payloadType: String, - val methodName: String, - val imports: List = emptyList(), - val topicPropertyKey: String, - ) : GeneratorItem - data class KafkaProducerClass( override val name: String, override val packageName: String, @@ -62,7 +49,6 @@ sealed interface GeneratorItem { val sendMethods: List, val kafkaValueType: String, val imports: List = emptyList(), - val topicPropertyKey: String, ) : GeneratorItem data class HandlerMethod( diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt index 9604141b..d0c8661a 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/factory/KotlinSpringKafkaModelFactory.kt @@ -90,7 +90,6 @@ class KotlinSpringKafkaModelFactory( sendMethods = listOf(sendMethod), kafkaValueType = payload.payloadType, imports = imports, - topicPropertyKey = "", ), ) } diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt index a6b5e128..56040e78 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/model/GeneratorItem.kt @@ -44,19 +44,6 @@ sealed interface GeneratorItem { val imports: List = emptyList(), ) : GeneratorItem - data class KafkaListenerClass( - override val name: String, - override val packageName: String, - override val description: List, - val topic: String, - val groupId: String, - val handlerInterface: String, // The interface to inject - val payloadType: String, - val methodName: String, - val imports: List = emptyList(), - val topicPropertyKey: String, - ) : GeneratorItem - data class KafkaProducerClass( override val name: String, override val packageName: String, @@ -65,7 +52,6 @@ sealed interface GeneratorItem { val sendMethods: List, val kafkaValueType: String, val imports: List = emptyList(), - val topicPropertyKey: String, ) : GeneratorItem data class HandlerMethod( diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt index c8523f55..8feb7773 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/kotlin/kafka/GenerateKotlinSpringKafkaOperationsTest.kt @@ -48,12 +48,12 @@ class GenerateKotlinSpringKafkaOperationsTest { ) assertFalse( outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), - "Listener should NOT exist" + "Consumer should NOT exist" ) } @Test - fun `should generate ONLY listener when isConsumer=true`() { + fun `should generate ONLY consumer when isConsumer=true`() { outputDir.deleteRecursively() val channel = @@ -80,7 +80,7 @@ class GenerateKotlinSpringKafkaOperationsTest { ) assertTrue( outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), - "Listener should exist" + "Consumer should exist" ) } @@ -112,7 +112,7 @@ class GenerateKotlinSpringKafkaOperationsTest { ) assertTrue( outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), - "Listener should exist" + "Consumer should exist" ) } @@ -144,7 +144,7 @@ class GenerateKotlinSpringKafkaOperationsTest { ) assertFalse( outputDir.resolve("$packagePath/consumer/EventsConsumer.kt").exists(), - "Listener should NOT exist" + "Consumer should NOT exist" ) } } From 4529f3b6528c69fa1dd861f834164e53ef003ed6 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:32:58 +0200 Subject: [PATCH 09/10] rename spring-kafka maven 'IT' test to contract scenario --- .../pom.xml | 8 ++++---- .../src/main/resources/asyncapi.yaml | 2 +- .../verify.groovy | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) rename asyncapi-generator-maven-plugin/src/test/it/{spring-kafka-simple => spring-kafka-contract}/pom.xml (84%) rename asyncapi-generator-maven-plugin/src/test/it/{spring-kafka-simple => spring-kafka-contract}/src/main/resources/asyncapi.yaml (90%) rename asyncapi-generator-maven-plugin/src/test/it/{spring-kafka-simple => spring-kafka-contract}/verify.groovy (74%) diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/pom.xml similarity index 84% rename from asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml rename to asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/pom.xml index 285d8ba9..5d1efa05 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/pom.xml +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 dev.banking.asyncapi.generator.it - spring-kafka-simple + spring-kafka-contract 1.0-SNAPSHOT @@ -13,7 +13,7 @@ 1.0-SNAPSHOT - spring-kafka-simple + spring-kafka-contract compile generate @@ -22,11 +22,11 @@ ${project.basedir}/src/main/resources/asyncapi.yaml kotlin - com.example.simple.model + com.example.contract.model - com.example.simple.client + com.example.contract.client true diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/src/main/resources/asyncapi.yaml b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/src/main/resources/asyncapi.yaml similarity index 90% rename from asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/src/main/resources/asyncapi.yaml rename to asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/src/main/resources/asyncapi.yaml index 572342f1..d925e6a6 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/src/main/resources/asyncapi.yaml +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/src/main/resources/asyncapi.yaml @@ -1,6 +1,6 @@ asyncapi: 3.0.0 info: - title: Spring Kafka Simple IT + title: Spring Kafka Contract IT version: 1.0.0 channels: userEvents: diff --git a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/verify.groovy similarity index 74% rename from asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy rename to asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/verify.groovy index cf7fb7f9..627d696b 100644 --- a/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-simple/verify.groovy +++ b/asyncapi-generator-maven-plugin/src/test/it/spring-kafka-contract/verify.groovy @@ -1,17 +1,17 @@ -def producer = new File(basedir, "target/generated-sources/asyncapi/com/example/simple/client/producer/UserEventsProducerUserSignedUp.kt") +def producer = new File(basedir, "target/generated-sources/asyncapi/com/example/contract/client/producer/UserEventsProducerUserSignedUp.kt") assert producer.exists() : "Expected UserEventsProducerUserSignedUp.kt to be generated" def producerContent = producer.text assert producerContent.contains("class UserEventsProducerUserSignedUp") : "Expected producer class name" assert producerContent.contains("KafkaTemplate") : "Expected typed KafkaTemplate" assert producerContent.contains("CompletableFuture>") : "Expected producer send future return type" -assert !producerContent.contains("@Component") : "Simple producer should not be annotated" +assert !producerContent.contains("@Component") : "Contract producer should not be annotated" -def consumer = new File(basedir, "target/generated-sources/asyncapi/com/example/simple/client/consumer/UserEventsConsumer.kt") +def consumer = new File(basedir, "target/generated-sources/asyncapi/com/example/contract/client/consumer/UserEventsConsumer.kt") assert consumer.exists() : "Expected UserEventsConsumer.kt to be generated" def consumerContent = consumer.text assert consumerContent.contains("interface UserEventsConsumer") : "Expected consumer interface" assert consumerContent.contains("fun onUserSignedUp") : "Expected onUserSignedUp method" assert !consumerContent.contains("{ }") : "Expected abstract consumer method" -assert !consumerContent.contains("@KafkaListener") : "Simple consumer should not be annotated" +assert !consumerContent.contains("@KafkaListener") : "Contract consumer should not be annotated" From 5a7b503c7b4cea70f15bcf83481534a95f1eff48 Mon Sep 17 00:00:00 2001 From: Salvador Bascunan Date: Thu, 18 Jun 2026 23:35:32 +0200 Subject: [PATCH 10/10] skip empty spring-kafka client generation tasks --- .../core/generator/plan/GenerationPlanner.kt | 4 +- .../generator/plan/GenerationPlannerTest.kt | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt index 8995c952..bcaeff5a 100644 --- a/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt +++ b/asyncapi-generator-core/src/main/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlanner.kt @@ -39,7 +39,7 @@ class GenerationPlanner { ), ) } - client.springKafka?.let { springKafka -> + client.springKafka?.takeIf { it.hasEnabledOutput() }?.let { springKafka -> add( GenerationTask.SpringKafkaClient( language = configuration.language, @@ -78,4 +78,6 @@ class GenerationPlanner { }, ) + private fun ClientGeneration.SpringKafka.hasEnabledOutput(): Boolean = + producer.enabled || consumer.enabled } diff --git a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt index 813b2231..d392e847 100644 --- a/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt +++ b/asyncapi-generator-core/src/test/kotlin/dev/banking/asyncapi/generator/core/generator/plan/GenerationPlannerTest.kt @@ -205,6 +205,57 @@ class GenerationPlannerTest { ) } + @Test + fun `plan skips Spring Kafka client task when producer and consumer are disabled`() { + val plan = + planner.plan( + generatorConfiguration( + clients = + listOf( + kafkaClientGeneration( + springKafka = + ClientGeneration.SpringKafka( + producer = ClientGeneration.Producer(enabled = false), + consumer = ClientGeneration.Consumer(enabled = false), + ), + ), + ), + ), + ) + + assertEquals( + listOf( + GenerationTask.HeaderModelArtifacts( + language = GeneratorName.KOTLIN, + packageName = "com.example.client.header", + ), + ), + plan.tasks, + ) + } + + @Test + fun `plan skips Kafka tasks when every Kafka capability is disabled`() { + val plan = + planner.plan( + generatorConfiguration( + clients = + listOf( + kafkaClientGeneration( + headers = ClientGeneration.Headers(enabled = false), + springKafka = + ClientGeneration.SpringKafka( + producer = ClientGeneration.Producer(enabled = false), + consumer = ClientGeneration.Consumer(enabled = false), + ), + ), + ), + ), + ) + + assertEquals(emptyList(), plan.tasks) + } + @Test fun `plan uses selected language for language-specific tasks`() { val plan =