From 24507e01991bc8e12069be5d28cc2f29d1fed436 Mon Sep 17 00:00:00 2001 From: Pascal Dal Farra Date: Wed, 5 Jun 2024 11:36:48 +0200 Subject: [PATCH 01/10] test(amqp): cleanup test(amqp): update e2e test(amqp): wait for ready amqp server chore(amqp): add spring-messaging dependency in example test(amqp): persist patched asyncapi.yaml chore(amqp): use non-exclusive queue in example test: add WaitStrategy for ApiSystemTest feat(ui): show channel bindings test(amqp): update asyncapi artifacts chore(ui): fix formatting test(ui): use valid mock data test(amqp): update asyncapi artifacts Part of GH-366 feat(amqp): scan all queues In addition to the routingKeys chore: update asyncapi.yaml gradle script chore(amqp): simplify local testing chore(amqp): fix queue configuration tests: additional RabbitListener tests tests: refactoring + add yaml endpoint testing --- build.gradle | 3 + .../SpringwolfInitApplicationListener.java | 4 +- .../e2e/tests/publishing.spec.ts | 2 + .../springwolf-amqp-example/build.gradle | 1 + .../docker-compose.yml | 2 + .../examples/amqp/AmqpConstants.java | 41 ++ .../configuration/RabbitConfiguration.java | 18 +- .../amqp/consumers/ExampleConsumer.java | 97 ++++- .../examples/amqp/dtos/GenericPayloadDto.java | 19 + .../amqp/producers/AnotherProducer.java | 8 +- .../src/main/resources/application.properties | 2 +- .../examples/amqp/ApiIntegrationTest.java | 37 +- .../examples/amqp/ApiSystemTest.java | 2 + .../examples/amqp/ProducerSystemTest.java | 4 +- .../amqp/SpringContextIntegrationTest.java | 2 +- .../src/test/resources/asyncapi.json | 231 ++++++++++ .../src/test/resources/asyncapi.yaml | 406 ++++++++++++++++++ .../examples/cloudstream/ApiSystemTest.java | 2 + .../examples/jms/ApiSystemTest.java | 2 + .../examples/kafka/ApiSystemTest.java | 2 + .../examples/sns/ApiSystemTest.java | 2 + .../examples/sqs/ApiSystemTest.java | 2 + .../examples/stomp/stomp/ApiSystemTest.java | 2 + .../scanners/bindings/RabbitListenerUtil.java | 27 +- .../channels/RabbitQueueBeanScanner.java | 30 ++ .../SpringwolfAmqpScannerConfiguration.java | 11 + .../channels/RabbitQueueBeanScannerTest.java | 47 ++ .../channel-main.component.spec.ts | 4 +- .../channel-operation.component.spec.ts | 4 +- .../new/channels/channels.component.html | 16 + .../new/channels/channels.component.ts | 12 +- .../asyncapi/asyncapi-mapper.service.ts | 2 +- .../service/asyncapi/models/channels.model.ts | 2 +- .../app/service/mock/mock-asyncapi.service.ts | 2 +- 34 files changed, 1007 insertions(+), 41 deletions(-) create mode 100644 springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/AmqpConstants.java create mode 100644 springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/dtos/GenericPayloadDto.java create mode 100644 springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml create mode 100644 springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java create mode 100644 springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java diff --git a/build.gradle b/build.gradle index 06044eaf4..c95af39a1 100644 --- a/build.gradle +++ b/build.gradle @@ -162,6 +162,9 @@ allprojects { project .file('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.actual.json') .renameTo('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.json') + project + .file('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.actual.yaml') + .renameTo('springwolf-examples/' + it + '-example/src/test/resources/asyncapi.yaml') } } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/SpringwolfInitApplicationListener.java b/springwolf-core/src/main/java/io/github/springwolf/core/SpringwolfInitApplicationListener.java index 952bf8f71..db68ab950 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/SpringwolfInitApplicationListener.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/SpringwolfInitApplicationListener.java @@ -24,10 +24,10 @@ public class SpringwolfInitApplicationListener implements ApplicationListener + * Note:
+ * The 'Default exchange' is a 'Direct exchange' with an empty name ( name= "") + */ + public static final String EXCHANGE_DEFAULT_EXCHANGE = ""; + + // Routing keys + /** + * When a queue is bound with "#" (hash) binding key, + * it will receive all the messages, regardless of the routing key - like in fanout exchange. + */ + public static final String ROUTING_KEY_ALL_MESSAGES = "#"; + + public static final String ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY = "example-topic-routing-key"; + + // Queues + public static final String QUEUE_EXAMPLE_QUEUE = "example-queue"; + public static final String QUEUE_ANOTHER_QUEUE = "another-queue"; + public static final String QUEUE_MULTI_PAYLOAD_QUEUE = "multi-payload-queue"; + public static final String QUEUE_EXAMPLE_BINDINGS_QUEUE = "example-bindings-queue"; + + public static final String QUEUE_CREATE = "queue-create"; + public static final String QUEUE_READ = "queue-read"; + public static final String QUEUE_DELETE = "queue-delete"; + public static final String QUEUE_UPDATE = "queue-update"; +} diff --git a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java index 2e92ce5e8..adfe68e26 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java +++ b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.examples.amqp.configuration; +import io.github.springwolf.examples.amqp.AmqpConstants; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Exchange; @@ -23,34 +24,39 @@ public Jackson2JsonMessageConverter converter() { @Bean public Queue exampleQueue() { - return new Queue("example-queue", false); + return new Queue(AmqpConstants.QUEUE_EXAMPLE_QUEUE, false); } @Bean public Queue anotherQueue() { - return new Queue("another-queue", false); + return new Queue(AmqpConstants.QUEUE_ANOTHER_QUEUE, false); } @Bean public Queue exampleBindingsQueue() { - return new Queue("example-bindings-queue", false, true, true); + return new Queue(AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, false, false, true); + } + + @Bean + public Queue queueRead() { + return new Queue(AmqpConstants.QUEUE_READ, false); } @Bean public Exchange exampleTopicExchange() { - return new TopicExchange("example-topic-exchange"); + return new TopicExchange(AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE); } @Bean public Queue multiPayloadQueue() { - return new Queue("multi-payload-queue"); + return new Queue(AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE); } @Bean public Binding exampleTopicBinding(Queue exampleBindingsQueue, Exchange exampleTopicExchange) { return BindingBuilder.bind(exampleBindingsQueue) .to(exampleTopicExchange) - .with("example-topic-routing-key") + .with(AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY) .noargs(); } } diff --git a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/consumers/ExampleConsumer.java b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/consumers/ExampleConsumer.java index fddf77bc0..79416adbc 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/consumers/ExampleConsumer.java +++ b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/consumers/ExampleConsumer.java @@ -1,16 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.examples.amqp.consumers; +import io.github.springwolf.examples.amqp.AmqpConstants; import io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto; import io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto; +import io.github.springwolf.examples.amqp.dtos.GenericPayloadDto; import io.github.springwolf.examples.amqp.producers.AnotherProducer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.ExchangeTypes; +import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.messaging.handler.annotation.Payload; import org.springframework.stereotype.Component; @Component @@ -20,9 +24,9 @@ public class ExampleConsumer { private final AnotherProducer anotherProducer; - @RabbitListener(queues = "example-queue") + @RabbitListener(queues = AmqpConstants.QUEUE_EXAMPLE_QUEUE) public void receiveExamplePayload(ExamplePayloadDto payload) { - log.info("Received new message in example-queue: {}", payload.toString()); + log.info("Received new message in {}: {}", AmqpConstants.QUEUE_EXAMPLE_QUEUE, payload.toString()); AnotherPayloadDto example = new AnotherPayloadDto(); example.setExample(payload); @@ -31,37 +35,102 @@ public void receiveExamplePayload(ExamplePayloadDto payload) { anotherProducer.sendMessage(example); } - @RabbitListener(queues = "another-queue") + @RabbitListener(queues = AmqpConstants.QUEUE_ANOTHER_QUEUE) public void receiveAnotherPayload(AnotherPayloadDto payload) { - log.info("Received new message in another-queue: {}", payload.toString()); + log.info("Received new message in {}: {}", AmqpConstants.QUEUE_ANOTHER_QUEUE, payload.toString()); } @RabbitListener( bindings = { @QueueBinding( - exchange = @Exchange(name = "example-topic-exchange", type = ExchangeTypes.TOPIC), + exchange = + @Exchange( + name = AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE, + type = ExchangeTypes.TOPIC), value = @Queue( - name = "example-bindings-queue", + name = AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, durable = "false", - exclusive = "true", + exclusive = "false", autoDelete = "true"), - key = "example-topic-routing-key") + key = AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY) }) public void bindingsExample(AnotherPayloadDto payload) { log.info( - "Received new message in example-bindings-queue" - + " through exchange example-topic-exchange using routing key example-topic-routing-key: {}", + "Received new message in {}" + " through exchange {}" + " using routing key {}: {}", + AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, + AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE, + AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY, payload.toString()); } - @RabbitListener(queues = "multi-payload-queue") + @RabbitListener(queues = AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE) public void bindingsBeanExample(AnotherPayloadDto payload) { - log.info("Received new message in multi-payload-queue (AnotherPayloadDto): {}", payload.toString()); + log.info( + "Received new message in {} (AnotherPayloadDto): {}", + AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE, + payload.toString()); } - @RabbitListener(queues = "multi-payload-queue") + @RabbitListener(queues = AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE) public void bindingsBeanExample(ExamplePayloadDto payload) { - log.info("Received new message in multi-payload-queue (ExamplePayloadDto): {}", payload.toString()); + log.info( + "Received new message in {} (ExamplePayloadDto): {}", + AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE, + payload.toString()); + } + + @RabbitListener(queuesToDeclare = @Queue(name = AmqpConstants.QUEUE_CREATE, autoDelete = "false", durable = "true")) + public void queuesToDeclareCreate(Message message, @Payload GenericPayloadDto payload) { + log.info( + "Received new message {} in {} (GenericPayloadDto): {}", + message, + AmqpConstants.QUEUE_CREATE, + payload.toString()); + } + + @RabbitListener(queuesToDeclare = @Queue(name = AmqpConstants.QUEUE_DELETE, autoDelete = "false", durable = "true")) + public void queuesToDeclareDelete(Message message, @Payload GenericPayloadDto payload) { + log.info( + "Received new message {} in {} (GenericPayloadDto): {}", + message, + AmqpConstants.QUEUE_DELETE, + payload.toString()); + } + + @RabbitListener( + autoStartup = "false", + bindings = + @QueueBinding( + exchange = + @Exchange( + name = AmqpConstants.EXCHANGE_CRUD_TOPIC_EXCHANGE_1, + type = ExchangeTypes.TOPIC), + key = AmqpConstants.ROUTING_KEY_ALL_MESSAGES, + value = @Queue(name = AmqpConstants.QUEUE_UPDATE, durable = "true", autoDelete = "false"))) + public void bindingsUpdate(Message message, @Payload GenericPayloadDto payload) { + log.info( + "Received new message {} in {} (GenericPayloadDto): {}", + message, + AmqpConstants.QUEUE_UPDATE, + payload.toString()); + } + + @RabbitListener( + autoStartup = "false", + bindings = + @QueueBinding( + exchange = + @Exchange( + name = AmqpConstants.EXCHANGE_CRUD_TOPIC_EXCHANGE_2, + type = ExchangeTypes.TOPIC), + key = AmqpConstants.ROUTING_KEY_ALL_MESSAGES, + value = @Queue(name = AmqpConstants.QUEUE_READ, durable = "false", autoDelete = "false"))) + public void bindingsRead(Message message, @Payload ExamplePayloadDto payload) { + log.info( + "Received new message {} in {} (ExamplePayloadDto): {}", + message, + AmqpConstants.QUEUE_UPDATE, + payload.toString()); } } diff --git a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/dtos/GenericPayloadDto.java b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/dtos/GenericPayloadDto.java new file mode 100644 index 000000000..c376517a9 --- /dev/null +++ b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/dtos/GenericPayloadDto.java @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.examples.amqp.dtos; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Schema(description = "Generic payload model") +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GenericPayloadDto { + + @Schema(description = "Generic Payload field", requiredMode = REQUIRED) + private T genericValue; +} diff --git a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/producers/AnotherProducer.java b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/producers/AnotherProducer.java index c2b200ae3..e8cc5cbf0 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/producers/AnotherProducer.java +++ b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/producers/AnotherProducer.java @@ -4,6 +4,7 @@ import io.github.springwolf.bindings.amqp.annotations.AmqpAsyncOperationBinding; import io.github.springwolf.core.asyncapi.annotations.AsyncOperation; import io.github.springwolf.core.asyncapi.annotations.AsyncPublisher; +import io.github.springwolf.examples.amqp.AmqpConstants; import io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto; import lombok.RequiredArgsConstructor; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -17,11 +18,14 @@ public class AnotherProducer { @AsyncPublisher( operation = @AsyncOperation( - channelName = "example-topic-exchange", + channelName = AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE, description = "Custom, optional description defined in the AsyncPublisher annotation")) @AmqpAsyncOperationBinding() public void sendMessage(AnotherPayloadDto msg) { // send - rabbitTemplate.convertAndSend("example-topic-exchange", "example-topic-routing-key", msg); + rabbitTemplate.convertAndSend( + AmqpConstants.EXCHANGE_EXAMPLE_TOPIC_EXCHANGE, + AmqpConstants.ROUTING_KEY_EXAMPLE_TOPIC_ROUTING_KEY, + msg); } } diff --git a/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties b/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties index fe58d88d9..b8d3f8122 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties @@ -5,7 +5,7 @@ spring.application.name=Springwolf example project - AMQP ######### # Spring amqp configuration -spring.rabbitmq.host=amqp +spring.rabbitmq.host=${AMQP_HOST:localhost} spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiIntegrationTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiIntegrationTest.java index 26025d272..75c11c29c 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiIntegrationTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiIntegrationTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.ActiveProfiles; @@ -24,15 +25,41 @@ class ApiIntegrationTest { @Autowired private TestRestTemplate restTemplate; + @Value("${spring.rabbitmq.host}") + public String amqpHost; + + @Value("${spring.rabbitmq.port}") + public String amqpPort; + @Test - void asyncApiResourceArtifactTest() throws IOException { + void asyncApiResourceJsonArtifactTest() throws IOException { String url = "/springwolf/docs"; String actual = restTemplate.getForObject(url, String.class); - Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actual); + String actualPatched = actual.replace(amqpHost + ":" + amqpPort, "amqp:5672"); + Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); + + String expected; + try (InputStream s = this.getClass().getResourceAsStream("/asyncapi.json")) { + assert s != null; + expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).trim(); + } + + assertEquals(expected, actualPatched); + } + + @Test + void asyncApiResourceYamlArtifactTest() throws IOException { + String url = "/springwolf/docs.yaml"; + String actual = restTemplate.getForObject(url, String.class); + String actualPatched = actual.replace(amqpHost + ":" + amqpPort, "amqp:5672"); + Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.yaml"), actualPatched); - InputStream s = this.getClass().getResourceAsStream("/asyncapi.json"); - String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).trim(); + String expected; + try (InputStream s = this.getClass().getResourceAsStream("/asyncapi.yaml")) { + assert s != null; + expected = new String(s.readAllBytes(), StandardCharsets.UTF_8).trim() + "\n"; + } - assertEquals(expected, actual); + assertEquals(expected, actualPatched); } } diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiSystemTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiSystemTest.java index acf71965a..0a9756007 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiSystemTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ProducerSystemTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ProducerSystemTest.java index dce74e9f5..d47631301 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ProducerSystemTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/ProducerSystemTest.java @@ -19,6 +19,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -57,6 +58,7 @@ public class ProducerSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withServices(AMQP_NAME) + .waitingFor(AMQP_NAME, Wait.forLogMessage(".*Server startup complete.*", 1)) .withLogConsumer(AMQP_NAME, l -> log.debug("amqp: {}", l.getUtf8StringWithoutLineEnding())); @Test @@ -79,7 +81,7 @@ void producerCanUseSpringwolfConfigurationToSendMessage() { payload.setSomeEnum(FOO1); // when - springwolfAmqpProducer.send("example-queue", payload); + springwolfAmqpProducer.send(AmqpConstants.QUEUE_EXAMPLE_QUEUE, payload); // then verify(exampleConsumer, timeout(10000)).receiveExamplePayload(payload); diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java index 1554babb9..f38bccbf6 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java @@ -44,7 +44,7 @@ void testContextWithApplicationProperties() { @Test void testAllChannelsAreFound() { - assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(5); + assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(11); } } diff --git a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json index 72a57381d..8deb3416f 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json @@ -25,6 +25,30 @@ } }, "channels": { + "#": { + "address": "#", + "messages": { + "io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + }, + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + }, + "bindings": { + "amqp": { + "is": "routingKey", + "exchange": { + "name": "CRUD-topic-exchange-1", + "type": "topic", + "durable": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } + }, "another-queue": { "address": "another-queue", "messages": { @@ -46,6 +70,22 @@ } } }, + "example-bindings-queue": { + "address": "example-bindings-queue", + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "name": "example-bindings-queue", + "durable": false, + "exclusive": false, + "autoDelete": true, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } + }, "example-queue": { "address": "example-queue", "messages": { @@ -119,6 +159,80 @@ "bindingVersion": "0.3.0" } } + }, + "queue-create": { + "address": "queue-create", + "messages": { + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + }, + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "name": "queue-create", + "durable": true, + "exclusive": false, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } + }, + "queue-delete": { + "address": "queue-delete", + "messages": { + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + }, + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "name": "queue-delete", + "durable": true, + "exclusive": false, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } + }, + "queue-read": { + "address": "queue-read", + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "name": "queue-read", + "durable": false, + "exclusive": false, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } + }, + "queue-update": { + "address": "queue-update", + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "name": "queue-update", + "durable": true, + "exclusive": false, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + } } }, "components": { @@ -216,6 +330,25 @@ "someEnum", "someString" ] + }, + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "title": "GenericPayloadDto", + "type": "object", + "properties": { + "genericValue": { + "type": "object", + "description": "Generic Payload field" + } + }, + "description": "Generic payload model", + "examples": [ + { + "genericValue": { } + } + ], + "required": [ + "genericValue" + ] } }, "messages": { @@ -255,10 +388,68 @@ "bindingVersion": "0.3.0" } } + }, + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "headers": { + "$ref": "#/components/schemas/SpringRabbitListenerDefaultHeaders" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + }, + "name": "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto", + "title": "GenericPayloadDto", + "bindings": { + "amqp": { + "bindingVersion": "0.3.0" + } + } } } }, "operations": { + "#_receive_bindingsRead": { + "action": "receive", + "channel": { + "$ref": "#/channels/#" + }, + "bindings": { + "amqp": { + "expiration": 0, + "cc": [ + "#" + ], + "bindingVersion": "0.3.0" + } + }, + "messages": [ + { + "$ref": "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + } + ] + }, + "#_receive_bindingsUpdate": { + "action": "receive", + "channel": { + "$ref": "#/channels/#" + }, + "bindings": { + "amqp": { + "expiration": 0, + "cc": [ + "#" + ], + "bindingVersion": "0.3.0" + } + }, + "messages": [ + { + "$ref": "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + ] + }, "another-queue_receive_receiveAnotherPayload": { "action": "receive", "channel": { @@ -366,6 +557,46 @@ "$ref": "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" } ] + }, + "queue-create_receive_queuesToDeclareCreate": { + "action": "receive", + "channel": { + "$ref": "#/channels/queue-create" + }, + "bindings": { + "amqp": { + "expiration": 0, + "cc": [ + "queue-create" + ], + "bindingVersion": "0.3.0" + } + }, + "messages": [ + { + "$ref": "#/channels/queue-create/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + ] + }, + "queue-delete_receive_queuesToDeclareDelete": { + "action": "receive", + "channel": { + "$ref": "#/channels/queue-delete" + }, + "bindings": { + "amqp": { + "expiration": 0, + "cc": [ + "queue-delete" + ], + "bindingVersion": "0.3.0" + } + }, + "messages": [ + { + "$ref": "#/channels/queue-delete/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + ] } } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml new file mode 100644 index 000000000..ca18082d5 --- /dev/null +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml @@ -0,0 +1,406 @@ +asyncapi: 3.0.0 +info: + title: Springwolf example project - AMQP + version: 1.0.0 + description: Springwolf example project to demonstrate springwolfs abilities + contact: + name: springwolf + url: https://github.com/springwolf/springwolf-core + email: example@example.com + x-phone: +49 123 456789 + license: + name: Apache License 2.0 + x-desc: some description + x-api-audience: company-internal + x-generator: springwolf +defaultContentType: application/json +servers: + amqp-server: + host: amqp:5672 + protocol: amqp +channels: + '#': + address: "#" + messages: + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + bindings: + amqp: + is: routingKey + exchange: + name: CRUD-topic-exchange-1 + type: topic + durable: true + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + another-queue: + address: another-queue + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + bindings: + amqp: + is: queue + queue: + name: another-queue + durable: false + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + example-bindings-queue: + address: example-bindings-queue + bindings: + amqp: + is: queue + queue: + name: example-bindings-queue + durable: false + exclusive: false + autoDelete: true + vhost: / + bindingVersion: 0.3.0 + example-queue: + address: example-queue + messages: + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + bindings: + amqp: + is: queue + queue: + name: example-queue + durable: false + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + example-topic-exchange: + address: example-topic-exchange + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + example-topic-routing-key: + address: example-topic-routing-key + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + bindings: + amqp: + is: routingKey + exchange: + name: example-topic-exchange + type: topic + durable: true + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + multi-payload-queue: + address: multi-payload-queue + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + bindings: + amqp: + is: queue + queue: + name: multi-payload-queue + durable: true + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + queue-create: + address: queue-create + messages: + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + bindings: + amqp: + is: queue + queue: + name: queue-create + durable: true + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + queue-delete: + address: queue-delete + messages: + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + bindings: + amqp: + is: queue + queue: + name: queue-delete + durable: true + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + queue-read: + address: queue-read + bindings: + amqp: + is: queue + queue: + name: queue-read + durable: false + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 + queue-update: + address: queue-update + bindings: + amqp: + is: queue + queue: + name: queue-update + durable: true + exclusive: false + autoDelete: false + vhost: / + bindingVersion: 0.3.0 +components: + schemas: + HeadersNotDocumented: + title: HeadersNotDocumented + type: object + properties: {} + description: "There can be headers, but they are not explicitly documented." + examples: + - {} + SpringRabbitListenerDefaultHeaders: + title: SpringRabbitListenerDefaultHeaders + type: object + properties: {} + examples: + - {} + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + title: AnotherPayloadDto + type: object + properties: + example: + $ref: "#/components/schemas/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + foo: + type: string + description: Foo field + maxLength: 100 + examples: + - bar + description: Another payload model + examples: + - example: + someEnum: FOO2 + someLong: 5 + someString: some string value + foo: bar + required: + - example + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + title: ExamplePayloadDto + type: object + properties: + someEnum: + type: string + description: Some enum field + enum: + - FOO1 + - FOO2 + - FOO3 + examples: + - FOO2 + someLong: + type: integer + description: Some long field + format: int64 + minimum: 0 + examples: + - 5 + someString: + type: string + description: Some string field + examples: + - some string value + description: Example payload model + examples: + - someEnum: FOO2 + someLong: 5 + someString: some string value + required: + - someEnum + - someString + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + title: GenericPayloadDto + type: object + properties: + genericValue: + type: object + description: Generic Payload field + description: Generic payload model + examples: + - genericValue: {} + required: + - genericValue + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + headers: + $ref: "#/components/schemas/HeadersNotDocumented" + payload: + schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0 + schema: + $ref: "#/components/schemas/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + name: io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto + title: AnotherPayloadDto + description: Another payload model + bindings: + amqp: + bindingVersion: 0.3.0 + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + headers: + $ref: "#/components/schemas/SpringRabbitListenerDefaultHeaders" + payload: + schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0 + schema: + $ref: "#/components/schemas/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + name: io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto + title: ExamplePayloadDto + bindings: + amqp: + bindingVersion: 0.3.0 + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + headers: + $ref: "#/components/schemas/SpringRabbitListenerDefaultHeaders" + payload: + schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0 + schema: + $ref: "#/components/schemas/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + name: io.github.springwolf.examples.amqp.dtos.GenericPayloadDto + title: GenericPayloadDto + bindings: + amqp: + bindingVersion: 0.3.0 +operations: + '#_receive_bindingsRead': + action: receive + channel: + $ref: "#/channels/#" + bindings: + amqp: + expiration: 0 + cc: + - "#" + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + '#_receive_bindingsUpdate': + action: receive + channel: + $ref: "#/channels/#" + bindings: + amqp: + expiration: 0 + cc: + - "#" + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + another-queue_receive_receiveAnotherPayload: + action: receive + channel: + $ref: "#/channels/another-queue" + bindings: + amqp: + expiration: 0 + cc: + - another-queue + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/another-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + example-queue_receive_receiveExamplePayload: + action: receive + channel: + $ref: "#/channels/example-queue" + bindings: + amqp: + expiration: 0 + cc: + - example-queue + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/example-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + example-topic-exchange_send_sendMessage: + action: send + channel: + $ref: "#/channels/example-topic-exchange" + title: example-topic-exchange_send + description: "Custom, optional description defined in the AsyncPublisher annotation" + bindings: + amqp: + expiration: 0 + cc: [] + priority: 0 + deliveryMode: 1 + mandatory: false + timestamp: false + ack: false + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/example-topic-exchange/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + example-topic-routing-key_receive_bindingsExample: + action: receive + channel: + $ref: "#/channels/example-topic-routing-key" + bindings: + amqp: + expiration: 0 + cc: + - example-topic-routing-key + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/example-topic-routing-key/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + multi-payload-queue_receive_bindingsBeanExample: + action: receive + channel: + $ref: "#/channels/multi-payload-queue" + bindings: + amqp: + expiration: 0 + cc: + - multi-payload-queue + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + - $ref: "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + queue-create_receive_queuesToDeclareCreate: + action: receive + channel: + $ref: "#/channels/queue-create" + bindings: + amqp: + expiration: 0 + cc: + - queue-create + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/queue-create/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + queue-delete_receive_queuesToDeclareDelete: + action: receive + channel: + $ref: "#/channels/queue-delete" + bindings: + amqp: + expiration: 0 + cc: + - queue-delete + bindingVersion: 0.3.0 + messages: + - $ref: "#/channels/queue-delete/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" diff --git a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/springwolf/examples/cloudstream/ApiSystemTest.java b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/springwolf/examples/cloudstream/ApiSystemTest.java index 6845d6753..f8e4b823f 100644 --- a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/springwolf/examples/cloudstream/ApiSystemTest.java +++ b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/springwolf/examples/cloudstream/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/springwolf/examples/jms/ApiSystemTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/springwolf/examples/jms/ApiSystemTest.java index 8c458f201..f6a6a3d4a 100644 --- a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/springwolf/examples/jms/ApiSystemTest.java +++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/springwolf/examples/jms/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/ApiSystemTest.java b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/ApiSystemTest.java index 48319c403..053cf52aa 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/ApiSystemTest.java +++ b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/springwolf/examples/kafka/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-sns-example/src/test/java/io/github/springwolf/examples/sns/ApiSystemTest.java b/springwolf-examples/springwolf-sns-example/src/test/java/io/github/springwolf/examples/sns/ApiSystemTest.java index 415ce0ea0..ba9c856e6 100644 --- a/springwolf-examples/springwolf-sns-example/src/test/java/io/github/springwolf/examples/sns/ApiSystemTest.java +++ b/springwolf-examples/springwolf-sns-example/src/test/java/io/github/springwolf/examples/sns/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/ApiSystemTest.java b/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/ApiSystemTest.java index e6f6130de..bdae15593 100644 --- a/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/ApiSystemTest.java +++ b/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -47,6 +48,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-examples/springwolf-stomp-example/src/test/java/io/github/springwolf/examples/stomp/stomp/ApiSystemTest.java b/springwolf-examples/springwolf-stomp-example/src/test/java/io/github/springwolf/examples/stomp/stomp/ApiSystemTest.java index 4b01a5fce..d8a8d5723 100644 --- a/springwolf-examples/springwolf-stomp-example/src/test/java/io/github/springwolf/examples/stomp/stomp/ApiSystemTest.java +++ b/springwolf-examples/springwolf-stomp-example/src/test/java/io/github/springwolf/examples/stomp/stomp/ApiSystemTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.springframework.web.client.RestTemplate; import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -45,6 +46,7 @@ public class ApiSystemTest { @Container public static DockerComposeContainer environment = new DockerComposeContainer<>(new File("docker-compose.yml")) .withExposedService(APP_NAME, APP_PORT) + .waitingFor(APP_NAME, Wait.forLogMessage(".*AsyncAPI document was built.*", 1)) .withEnv(ENV) .withLogConsumer(APP_NAME, l -> log.debug("APP: {}", l.getUtf8StringWithoutLineEnding())); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 09a6200a3..e5d925482 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -11,6 +11,8 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; @@ -33,6 +35,7 @@ @Slf4j public class RabbitListenerUtil { + public static final String BINDING_NAME = "amqp"; private static final Boolean DEFAULT_AUTO_DELETE = false; private static final Boolean DEFAULT_DURABLE = true; private static final Boolean DEFAULT_EXCLUSIVE = false; @@ -98,7 +101,7 @@ public static Map buildChannelBinding( channelBinding.exchange(buildExchangeProperties(annotation, exchangeName, context)); } - return Map.of("amqp", channelBinding.build()); + return Map.of(BINDING_NAME, channelBinding.build()); } private static AMQPChannelExchangeProperties buildExchangeProperties( @@ -163,6 +166,24 @@ private static AMQPChannelQueueProperties buildQueueProperties( .build(); } + public static ChannelObject buildChannelObject(org.springframework.amqp.core.Queue queue) { + return ChannelObject.builder() + .channelId(ReferenceUtil.toValidId(queue.getName())) + .address(queue.getName()) + .bindings(Map.of( + BINDING_NAME, + AMQPChannelBinding.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() + .name(queue.getName()) + .autoDelete(queue.isAutoDelete()) + .durable(queue.isDurable()) + .exclusive(queue.isExclusive()) + .build()) + .build())) + .build(); + } + private static Boolean parse(String value, Boolean defaultIfEmpty) { if ("".equals(value)) { return defaultIfEmpty; @@ -194,7 +215,7 @@ private static String getExchangeName( public static Map buildOperationBinding( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { return Map.of( - "amqp", + BINDING_NAME, AMQPOperationBinding.builder() .cc(getRoutingKeys(annotation, resolver, context)) .build()); @@ -234,7 +255,7 @@ private static List getRoutingKeys( public static Map buildMessageBinding() { // currently the feature to define amqp message binding is not implemented. - return Map.of("amqp", new AMQPMessageBinding()); + return Map.of(BINDING_NAME, new AMQPMessageBinding()); } public record RabbitListenerUtilContext( diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java new file mode 100644 index 000000000..79ef8726a --- /dev/null +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.amqp.asyncapi.scanners.channels; + +import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.springwolf.core.asyncapi.scanners.ChannelsScanner; +import io.github.springwolf.plugins.amqp.asyncapi.scanners.bindings.RabbitListenerUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.amqp.core.Queue; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class RabbitQueueBeanScanner implements ChannelsScanner { + private final List queues; + + @Override + public Map scan() { + return queues.stream() + .map(RabbitListenerUtil::buildChannelObject) + .collect(Collectors.toMap( + o -> ((AMQPChannelBinding) o.getBindings().get(RabbitListenerUtil.BINDING_NAME)) + .getQueue() + .getName(), + c -> c, + (a, b) -> a)); + } +} diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java index fa02891d0..67f84d920 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/configuration/SpringwolfAmqpScannerConfiguration.java @@ -14,6 +14,7 @@ import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner; import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationMethodLevelOperationsScanner; import io.github.springwolf.plugins.amqp.asyncapi.scanners.bindings.AmqpBindingFactory; +import io.github.springwolf.plugins.amqp.asyncapi.scanners.channels.RabbitQueueBeanScanner; import io.github.springwolf.plugins.amqp.asyncapi.scanners.common.headers.AsyncHeadersForAmqpBuilder; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; @@ -158,4 +159,14 @@ public SpringAnnotationOperationsScanner simpleRabbitMethodLevelListenerAnnotati return new SpringAnnotationOperationsScanner(springwolfClassScanner, strategy); } + + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_RABBIT_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public RabbitQueueBeanScanner rabbitQueueBeanScanner(List queues) { + return new RabbitQueueBeanScanner(queues); + } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java new file mode 100644 index 000000000..d12d8244b --- /dev/null +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.amqp.asyncapi.scanners.channels; + +import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelQueueProperties; +import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; +import org.junit.jupiter.api.Test; +import org.springframework.amqp.core.Queue; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class RabbitQueueBeanScannerTest { + + @Test + void scan() { + // given + var queue = new Queue("name"); + var scanner = new RabbitQueueBeanScanner(List.of(queue)); + + // when + var result = scanner.scan(); + + // then + assertThat(result) + .isEqualTo(Map.of( + "name", + ChannelObject.builder() + .channelId("name") + .address("name") + .bindings(Map.of( + "amqp", + AMQPChannelBinding.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() + .name("name") + .autoDelete(false) + .durable(true) + .exclusive(false) + .build()) + .build())) + .build())); + } +} diff --git a/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.spec.ts b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.spec.ts index 813afd2b9..dde13b8f8 100644 --- a/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.spec.ts +++ b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.spec.ts @@ -12,7 +12,9 @@ import { AsyncApiService } from "../../../service/asyncapi/asyncapi.service"; import { PublisherService } from "../../../service/publisher.service"; describe("ChannelMainComponent", () => { - const mockData = mockedExampleSchemaMapped.channelOperations[0]; + const mockData = mockedExampleSchemaMapped.channelOperations + .slice(-1) + .pop()!!; beforeEach(async () => { mockedAsyncApiService.getAsyncApi.mockClear(); diff --git a/springwolf-ui/src/app/components/new/channels/channel-main/channel-operation.component.spec.ts b/springwolf-ui/src/app/components/new/channels/channel-main/channel-operation.component.spec.ts index 4a59e7f4e..bd8fa88c4 100644 --- a/springwolf-ui/src/app/components/new/channels/channel-main/channel-operation.component.spec.ts +++ b/springwolf-ui/src/app/components/new/channels/channel-main/channel-operation.component.spec.ts @@ -16,7 +16,9 @@ import { } from "../../../mock-components.spec"; describe("ChannelOperationComponent", () => { - const mockData = mockedExampleSchemaMapped.channelOperations[0]; + const mockData = mockedExampleSchemaMapped.channelOperations + .slice(-1) + .pop()!!; beforeEach(async () => { mockedAsyncApiService.getAsyncApi.mockClear(); diff --git a/springwolf-ui/src/app/components/new/channels/channels.component.html b/springwolf-ui/src/app/components/new/channels/channels.component.html index 7ee69ca09..b213b6100 100644 --- a/springwolf-ui/src/app/components/new/channels/channels.component.html +++ b/springwolf-ui/src/app/components/new/channels/channels.component.html @@ -3,6 +3,22 @@

Channels

@for (channel of channels; track channel) {

{{ channel.name }}

+ +
+
+
Channel Binding
+
+
+
+ +
+
+
+ @for (channelOperation of channel.operations; track channelOperation) { { this.channels = asyncapi.channels; }); + + this.uiService.isShowBindings$.subscribe( + (value) => (this.isShowBindings = value) + ); } } diff --git a/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts index 612aa0496..509fe78ca 100644 --- a/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts +++ b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts @@ -258,7 +258,7 @@ export class AsyncApiMapperService { "message of channel " + channelName, () => { const messageId = this.resolveRefId(operationMessage.$ref); - const channelMessage = channel.messages[messageId]; + const channelMessage = channel.messages!![messageId]; const channelMessageId = this.resolveRefId(channelMessage.$ref); const message = messages[channelMessageId]; diff --git a/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts b/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts index caae40d9f..0a538c6dc 100644 --- a/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts +++ b/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts @@ -8,7 +8,7 @@ export interface ServerChannels { export interface ServerChannel { address: string; description?: string; - messages: { + messages?: { [key: string]: { $ref: string; }; diff --git a/springwolf-ui/src/app/service/mock/mock-asyncapi.service.ts b/springwolf-ui/src/app/service/mock/mock-asyncapi.service.ts index 6c140ad01..88be32023 100644 --- a/springwolf-ui/src/app/service/mock/mock-asyncapi.service.ts +++ b/springwolf-ui/src/app/service/mock/mock-asyncapi.service.ts @@ -8,7 +8,7 @@ const asyncApiMapperService = new AsyncApiMapperService({ showWarning: jest.fn(), }); export const mockedExampleSchemaMapped = asyncApiMapperService.toAsyncApi( - exampleSchemas[0].value + exampleSchemas.find((el) => el.plugin === "kafka")!!.value )!!; export const mockedAsyncApiService: { getAsyncApi: jest.Mock } = { getAsyncApi: jest.fn().mockReturnValue(of(mockedExampleSchemaMapped)), From ab41d6e5acfbd085dd88810c8a4adacad15087b7 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Wed, 31 Jul 2024 21:09:01 +0200 Subject: [PATCH 02/10] refactor(amqp): extract RabbitListenerUtilContext to own class --- .../scanners/bindings/AmqpBindingFactory.java | 4 +- .../scanners/bindings/RabbitListenerUtil.java | 61 +++--- .../bindings/RabbitListenerUtilContext.java | 27 +++ .../RabbitListenerUtilContextTest.java | 75 ++++++++ .../bindings/RabbitListenerUtilTest.java | 181 +++++++++--------- 5 files changed, 232 insertions(+), 116 deletions(-) create mode 100644 springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContext.java create mode 100644 springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContextTest.java diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java index 572de75a5..2df3dd760 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java @@ -17,11 +17,11 @@ import java.util.Map; public class AmqpBindingFactory implements BindingFactory, EmbeddedValueResolverAware { - private final RabbitListenerUtil.RabbitListenerUtilContext context; + private final RabbitListenerUtilContext context; private StringValueResolver stringValueResolver; public AmqpBindingFactory(List queues, List exchanges, List bindings) { - this.context = RabbitListenerUtil.RabbitListenerUtilContext.create(queues, exchanges, bindings); + this.context = RabbitListenerUtilContext.create(queues, exchanges, bindings); } @Override diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index e5d925482..29828a6be 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -29,10 +29,17 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * Note: bindings, queues, and queuesToDeclare are mutually exclusive + * @see RabbitListener + *

+ * How does rabbitmq work? + * 1. Producer sends a message to an exchange (default exchange if not specified) + * 2. Exchange routes the message to a queue based on the routing key (routing key = queue name if not specified) + * 3. Consumer consumes the message from the queue + */ @Slf4j public class RabbitListenerUtil { public static final String BINDING_NAME = "amqp"; @@ -41,7 +48,30 @@ public class RabbitListenerUtil { private static final Boolean DEFAULT_EXCLUSIVE = false; private static final String DEFAULT_EXCHANGE_TYPE = ExchangeTypes.DIRECT; + // RabbitListener -> multiple for one annotation (only use first, like kafka) + // either bindings OR queues OR queuesToDeclare + // -- queueBinding (annotation) <- specific exchange + // -- queue (like queuesToDeclare) + // -- exchange (annotatoin) + // -- key (array) + // -- queues (string) (can point to queue beans) + // -- queuesToDeclare (object) (annotation instead of queue bean, default exchange, routing key = queue name) + + // binding == exchange + // - direct exchange + // - binding_key of the queue = routing key + // - default exchange + // - queue = routing key + // - topic exchange + // - binding_pattern of the queue = routing key + // - fanout exchange + // - no routing key, copy to all bindings + + // can be combined with ReplyTo and SendTo annoation (like stomp) + public static String getChannelName(RabbitListener annotation, StringValueResolver resolver) { + // queueName_routingKey_exchange (second, third only when present) + Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.concat( Stream.of(binding.key()), // if routing key is configured, prefer it @@ -109,7 +139,7 @@ private static AMQPChannelExchangeProperties buildExchangeProperties( // When a bean is found, its values are preferred regardless of the annotations values. // When using the annotation, it is not possible to differentiate between user set and default parameters - Exchange exchange = context.exchangeMap.get(exchangeName); + Exchange exchange = context.exchangeMap().get(exchangeName); if (exchange != null) { return AMQPChannelExchangeProperties.builder() .name(exchangeName) @@ -141,7 +171,7 @@ private static AMQPChannelExchangeProperties buildExchangeProperties( private static AMQPChannelQueueProperties buildQueueProperties( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { String queueName = getQueueName(annotation, resolver); - org.springframework.amqp.core.Queue queue = context.queueMap.get(queueName); + org.springframework.amqp.core.Queue queue = context.queueMap().get(queueName); boolean autoDelete = queue != null ? queue.isAutoDelete() : DEFAULT_AUTO_DELETE; boolean durable = queue != null ? queue.isDurable() : DEFAULT_DURABLE; boolean exclusive = queue != null ? queue.isExclusive() : DEFAULT_EXCLUSIVE; @@ -199,7 +229,7 @@ private static String getExchangeName( .findFirst() .orElse(null); - Binding binding = context.bindingMap.get(getChannelName(annotation, resolver)); + Binding binding = context.bindingMap().get(getChannelName(annotation, resolver)); if (exchangeName == null && binding != null) { exchangeName = binding.getExchange(); } @@ -238,7 +268,7 @@ private static List getRoutingKeys( .findFirst() .orElse(null); - Binding binding = context.bindingMap.get(getChannelName(annotation, resolver)); + Binding binding = context.bindingMap().get(getChannelName(annotation, resolver)); if (routingKeys == null && binding != null) { routingKeys = Collections.singletonList(binding.getRoutingKey()); } @@ -257,23 +287,4 @@ public static Map buildMessageBinding() { // currently the feature to define amqp message binding is not implemented. return Map.of(BINDING_NAME, new AMQPMessageBinding()); } - - public record RabbitListenerUtilContext( - Map queueMap, - Map exchangeMap, - Map bindingMap) { - - public static RabbitListenerUtilContext create( - List queues, List exchanges, List bindings) { - Map queueMap = queues.stream() - .collect(Collectors.toMap( - org.springframework.amqp.core.Queue::getName, Function.identity(), (e1, e2) -> e1)); - Map exchangeMap = exchanges.stream() - .collect(Collectors.toMap(Exchange::getName, Function.identity(), (e1, e2) -> e1)); - Map bindingMap = bindings.stream() - .filter(Binding::isDestinationQueue) - .collect(Collectors.toMap(Binding::getDestination, Function.identity(), (e1, e2) -> e1)); - return new RabbitListenerUtil.RabbitListenerUtilContext(queueMap, exchangeMap, bindingMap); - } - } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContext.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContext.java new file mode 100644 index 000000000..c430d6aa2 --- /dev/null +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContext.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.amqp.asyncapi.scanners.bindings; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.Exchange; +import org.springframework.amqp.core.Queue; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public record RabbitListenerUtilContext( + Map queueMap, Map exchangeMap, Map bindingMap) { + + public static RabbitListenerUtilContext create( + List queues, List exchanges, List bindings) { + Map queueMap = + queues.stream().collect(Collectors.toMap(Queue::getName, Function.identity(), (e1, e2) -> e1)); + Map exchangeMap = + exchanges.stream().collect(Collectors.toMap(Exchange::getName, Function.identity(), (e1, e2) -> e1)); + Map bindingMap = bindings.stream() + .filter(Binding::isDestinationQueue) + .collect(Collectors.toMap(Binding::getDestination, Function.identity(), (e1, e2) -> e1)); + return new RabbitListenerUtilContext(queueMap, exchangeMap, bindingMap); + } +} diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContextTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContextTest.java new file mode 100644 index 000000000..deae8b049 --- /dev/null +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilContextTest.java @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.plugins.amqp.asyncapi.scanners.bindings; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.TopicExchange; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Nested +class RabbitListenerUtilContextTest { + @Test + void testEmptyContext() { + // when + RabbitListenerUtilContext context = RabbitListenerUtilContext.create(List.of(), List.of(), List.of()); + + // then + assertEquals(Map.of(), context.queueMap()); + assertEquals(Map.of(), context.exchangeMap()); + assertEquals(Map.of(), context.bindingMap()); + } + + @Test + void testWithSingleTopic() { + org.springframework.amqp.core.Queue queue = + new org.springframework.amqp.core.Queue("queue-1", false, true, true); + TopicExchange exchange = new TopicExchange("exchange-name", false, true); + Binding binding = BindingBuilder.bind(queue).to(exchange).with("routing-key"); + + // when + RabbitListenerUtilContext context = + RabbitListenerUtilContext.create(List.of(queue), List.of(exchange), List.of(binding)); + + // then + assertEquals(Map.of("queue-1", queue), context.queueMap()); + assertEquals(Map.of("exchange-name", exchange), context.exchangeMap()); + assertEquals(Map.of("queue-1", binding), context.bindingMap()); + } + + @Test + void testWithMultipleBeansForOneTopic() { + org.springframework.amqp.core.Queue queueBean = + new org.springframework.amqp.core.Queue("queue-1", false, true, true); + TopicExchange exchangeBean = new TopicExchange("exchange-name", false, true); + Binding bindingBean = BindingBuilder.bind(queueBean).to(exchangeBean).with("routing-key"); + + // In this test, annotation values are different compared to the beans. + // This might happen due to ill user configuration, but like Spring AMQP Springwolf tries to handle it + org.springframework.amqp.core.Queue queueAnnotation = + new org.springframework.amqp.core.Queue("queue-1", false, false, false); + TopicExchange exchangeAnnotation = new TopicExchange("exchange-name", true, false); + Binding bindingAnnotation = + BindingBuilder.bind(queueAnnotation).to(exchangeAnnotation).with("routing-key"); + + // when + RabbitListenerUtilContext context = RabbitListenerUtilContext.create( + List.of(queueBean, queueAnnotation), + List.of(exchangeBean, exchangeAnnotation), + List.of(bindingBean, bindingAnnotation)); + + // then + assertThat(context.queueMap()).hasSize(1); + assertThat(context.queueMap().get("queue-1")).isIn(queueBean, queueAnnotation); + assertThat(context.exchangeMap()).hasSize(1); + assertThat(context.exchangeMap().get("exchange-name")).isIn(exchangeBean, exchangeAnnotation); + assertThat(context.bindingMap()).hasSize(1); + assertThat(context.bindingMap().get("queue-1")).isIn(bindingBean, bindingAnnotation); + } +} diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java index a8bd9717d..71768cae0 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java @@ -14,9 +14,6 @@ import org.assertj.core.util.Sets; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.rabbit.annotation.Exchange; @@ -29,24 +26,23 @@ import java.util.Map; import static java.util.Collections.emptyMap; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class RabbitListenerUtilTest { - private final RabbitListenerUtil.RabbitListenerUtilContext emptyContext = - new RabbitListenerUtil.RabbitListenerUtilContext(emptyMap(), emptyMap(), emptyMap()); + private final RabbitListenerUtilContext emptyContext = + new RabbitListenerUtilContext(emptyMap(), emptyMap(), emptyMap()); @Nested class QueuesConfiguration { + // TODO: this must have spring beans for the queue (exchange =default) - @ParameterizedTest - @ValueSource(classes = {ClassWithQueuesConfiguration.class, ClassWithQueuesToDeclare.class}) - void getChannelName(Class clazz) { + @Test + void getChannelName() { // given - RabbitListener annotation = getAnnotation(clazz); + RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); @@ -57,11 +53,10 @@ void getChannelName(Class clazz) { assertEquals("queue-1", channelName); } - @ParameterizedTest - @ValueSource(classes = {ClassWithQueuesConfiguration.class, ClassWithQueuesToDeclare.class}) - void buildChannelBinding(Class clazz) { + @Test + void buildChannelBinding() { // given - RabbitListener annotation = getAnnotation(clazz); + RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); @@ -86,11 +81,10 @@ void buildChannelBinding(Class clazz) { channelBinding.get("amqp")); } - @ParameterizedTest - @ValueSource(classes = {ClassWithQueuesConfiguration.class, ClassWithQueuesToDeclare.class}) - void buildOperationBinding(Class clazz) { + @Test + void buildOperationBinding() { // given - RabbitListener annotation = getAnnotation(clazz); + RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); @@ -120,12 +114,83 @@ private static class ClassWithQueuesConfiguration { @RabbitListener(queues = "${queue-1}") private void methodWithAnnotation(String payload) {} } + } + + @Nested + class QueuesToDeclareConfiguration { + + @Test + void getChannelName() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); + + // then + assertEquals("queue-1", channelName); + } + + @Test + void buildChannelBinding() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + Map channelBinding = + RabbitListenerUtil.buildChannelBinding(annotation, resolver, emptyContext); + + // then + assertEquals(1, channelBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); + assertEquals( + AMQPChannelBinding.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() + .name("queue-1") + .durable(true) + .autoDelete(false) + .exclusive(false) + .vhost("/") + .build()) + .build(), + channelBinding.get("amqp")); + } + + @Test + void buildOperationBinding() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + } + + @Test + void buildMessageBinding() { + // when + Map messageBinding = RabbitListenerUtil.buildMessageBinding(); + + // then + assertEquals(1, messageBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), messageBinding.keySet()); + assertEquals(new AMQPMessageBinding(), messageBinding.get("amqp")); + } - /** - * Note: bindings, queues, and queuesToDeclare are mutually exclusive - * @see RabbitListener.queuesToDeclare - */ private static class ClassWithQueuesToDeclare { + // TODO: bean is not expected to be in the spring context (exchange= default) @RabbitListener(queuesToDeclare = @Queue(name = "${queue-1}")) private void methodWithAnnotation(String payload) {} } @@ -203,7 +268,8 @@ void buildMessageBinding() { } private static class ClassWithBindingsConfiguration { - + // TODO: queue must be in spring context + // TODO: exchange can be @RabbitListener( bindings = { @QueueBinding( @@ -301,14 +367,14 @@ private void methodWithAnnotation(String payload) {} @Nested class QueueBindingsWithBeansConfiguration { - private final RabbitListenerUtil.RabbitListenerUtilContext context; + private final RabbitListenerUtilContext context; - // Simulate a RabbitListenerUtilContext that has already been populated by exising spring beans + // Simulate a RabbitListenerUtilContext that has already been populated by existing spring beans { org.springframework.amqp.core.Queue queue = new org.springframework.amqp.core.Queue("queue-1", false, true, true); TopicExchange exchange = new TopicExchange("exchange-name", false, true); - context = RabbitListenerUtil.RabbitListenerUtilContext.create( + context = RabbitListenerUtilContext.create( List.of(queue), List.of(exchange), List.of(BindingBuilder.bind(queue).to(exchange).with("routing-key"))); @@ -400,69 +466,6 @@ private void methodWithAnnotation(String payload) {} } } - @Nested - class RabbitListenerUtilContextTest { - @Test - void testEmptyContext() { - // when - RabbitListenerUtil.RabbitListenerUtilContext context = - RabbitListenerUtil.RabbitListenerUtilContext.create(List.of(), List.of(), List.of()); - - // then - assertEquals(Map.of(), context.queueMap()); - assertEquals(Map.of(), context.exchangeMap()); - assertEquals(Map.of(), context.bindingMap()); - } - - @Test - void testWithSingleTopic() { - org.springframework.amqp.core.Queue queue = - new org.springframework.amqp.core.Queue("queue-1", false, true, true); - TopicExchange exchange = new TopicExchange("exchange-name", false, true); - Binding binding = BindingBuilder.bind(queue).to(exchange).with("routing-key"); - - // when - RabbitListenerUtil.RabbitListenerUtilContext context = RabbitListenerUtil.RabbitListenerUtilContext.create( - List.of(queue), List.of(exchange), List.of(binding)); - - // then - assertEquals(Map.of("queue-1", queue), context.queueMap()); - assertEquals(Map.of("exchange-name", exchange), context.exchangeMap()); - assertEquals(Map.of("queue-1", binding), context.bindingMap()); - } - - @Test - void testWithMultipleBeansForOneTopic() { - org.springframework.amqp.core.Queue queueBean = - new org.springframework.amqp.core.Queue("queue-1", false, true, true); - TopicExchange exchangeBean = new TopicExchange("exchange-name", false, true); - Binding bindingBean = - BindingBuilder.bind(queueBean).to(exchangeBean).with("routing-key"); - - // In this test, annotation values are different compared to the beans. - // This might happen due to ill user configuration, but like Spring AMQP Springwolf tries to handle it - org.springframework.amqp.core.Queue queueAnnotation = - new org.springframework.amqp.core.Queue("queue-1", false, false, false); - TopicExchange exchangeAnnotation = new TopicExchange("exchange-name", true, false); - Binding bindingAnnotation = - BindingBuilder.bind(queueAnnotation).to(exchangeAnnotation).with("routing-key"); - - // when - RabbitListenerUtil.RabbitListenerUtilContext context = RabbitListenerUtil.RabbitListenerUtilContext.create( - List.of(queueBean, queueAnnotation), - List.of(exchangeBean, exchangeAnnotation), - List.of(bindingBean, bindingAnnotation)); - - // then - assertThat(context.queueMap()).hasSize(1); - assertThat(context.queueMap().get("queue-1")).isIn(queueBean, queueAnnotation); - assertThat(context.exchangeMap()).hasSize(1); - assertThat(context.exchangeMap().get("exchange-name")).isIn(exchangeBean, exchangeAnnotation); - assertThat(context.bindingMap()).hasSize(1); - assertThat(context.bindingMap().get("queue-1")).isIn(bindingBean, bindingAnnotation); - } - } - private static RabbitListener getAnnotation(Class clazz) { return clazz.getDeclaredMethods()[0].getAnnotation(RabbitListener.class); } From eabc58fc85175e3b5e481eacb8e1cd9d04246738 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Wed, 31 Jul 2024 22:01:00 +0200 Subject: [PATCH 03/10] test(amqp): update test cases --- .../bindings/RabbitListenerUtilTest.java | 328 +++++++++++++----- 1 file changed, 234 insertions(+), 94 deletions(-) diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java index 71768cae0..c30df7309 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java @@ -12,9 +12,9 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import org.assertj.core.util.Sets; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.TopicExchange; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; @@ -35,9 +35,17 @@ class RabbitListenerUtilTest { private final RabbitListenerUtilContext emptyContext = new RabbitListenerUtilContext(emptyMap(), emptyMap(), emptyMap()); + private final org.springframework.amqp.core.Queue queue = + new org.springframework.amqp.core.Queue("queue-1", false, true, true); + private final RabbitListenerUtilContext queueContext = + RabbitListenerUtilContext.create(List.of(queue), List.of(), List.of()); + + private final TopicExchange exchange = new TopicExchange("exchange-name", false, true); + private final RabbitListenerUtilContext exchangeContext = + RabbitListenerUtilContext.create(List.of(queue), List.of(exchange), List.of()); + @Nested class QueuesConfiguration { - // TODO: this must have spring beans for the queue (exchange =default) @Test void getChannelName() { @@ -60,6 +68,35 @@ void buildChannelBinding() { StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when + Map channelBinding = + RabbitListenerUtil.buildChannelBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, channelBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); + assertEquals( + AMQPChannelBinding.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() + .name("queue-1") + .durable(false) + .autoDelete(true) + .exclusive(true) + .vhost("/") + .build()) + .build(), + channelBinding.get("amqp")); + } + + @Test + @Disabled("TODO: what to do with invalid configuration") + void buildChannelBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when Map channelBinding = RabbitListenerUtil.buildChannelBinding(annotation, resolver, emptyContext); @@ -88,6 +125,24 @@ void buildOperationBinding() { StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + } + + @Test + @Disabled("TODO: what to do with invalid configuration") + void buildOperationBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when Map operationBinding = RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); @@ -134,12 +189,41 @@ void getChannelName() { } @Test + @Disabled("TODO: what to do with invalid configuration") void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when + Map channelBinding = + RabbitListenerUtil.buildChannelBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, channelBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); + assertEquals( + AMQPChannelBinding.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() + .name("queue-1") + .durable(true) + .autoDelete(false) + .exclusive(false) + .vhost("/") + .build()) + .build(), + channelBinding.get("amqp")); + } + + @Test + void buildChannelBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when Map channelBinding = RabbitListenerUtil.buildChannelBinding(annotation, resolver, emptyContext); @@ -162,12 +246,30 @@ void buildChannelBinding() { } @Test + @Disabled("TODO: what to do with invalid configuration") void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + } + + @Test + void buildOperationBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + // when Map operationBinding = RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); @@ -190,7 +292,7 @@ void buildMessageBinding() { } private static class ClassWithQueuesToDeclare { - // TODO: bean is not expected to be in the spring context (exchange= default) + // Note: Bean should not be in the context as it is created (declared) by the annotation @RabbitListener(queuesToDeclare = @Queue(name = "${queue-1}")) private void methodWithAnnotation(String payload) {} } @@ -198,10 +300,11 @@ private void methodWithAnnotation(String payload) {} @Nested class QueueBindingsConfiguration { + @Test void getChannelName() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); @@ -210,18 +313,20 @@ void getChannelName() { // then assertEquals("queue-1", channelName); + // TODO: this should take into account the context + // "queue-1_#_exchange-name" } @Test void buildChannelBinding() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = - RabbitListenerUtil.buildChannelBinding(annotation, resolver, emptyContext); + RabbitListenerUtil.buildChannelBinding(annotation, resolver, queueContext); // then assertEquals(1, channelBinding.size()); @@ -231,7 +336,7 @@ void buildChannelBinding() { .is(AMQPChannelType.ROUTING_KEY) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(AMQPChannelExchangeType.TOPIC) + .type(AMQPChannelExchangeType.DIRECT) .durable(true) .autoDelete(false) .build()) @@ -240,66 +345,37 @@ void buildChannelBinding() { } @Test - void buildOperationBinding() { + void buildChannelBindingWithExchangeContext() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when - Map operationBinding = - RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); - - // then - assertEquals(1, operationBinding.size()); - assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); - } - - @Test - void buildMessageBinding() { - // when - Map messageBinding = RabbitListenerUtil.buildMessageBinding(); - - // then - assertEquals(1, messageBinding.size()); - assertEquals(Sets.newTreeSet("amqp"), messageBinding.keySet()); - assertEquals(new AMQPMessageBinding(), messageBinding.get("amqp")); - } - - private static class ClassWithBindingsConfiguration { - // TODO: queue must be in spring context - // TODO: exchange can be - @RabbitListener( - bindings = { - @QueueBinding( - exchange = @Exchange(name = "exchange-name", type = "topic"), - value = @Queue(name = "${queue-1}")) - }) - private void methodWithAnnotation(String payload) {} - } - } - - @Nested - class QueueBindingWithRoutingKeyConfiguration { - @Test - void getChannelName() { - // given - RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); - - // when - String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); + Map channelBinding = + RabbitListenerUtil.buildChannelBinding(annotation, resolver, exchangeContext); // then - assertEquals("routing-key", channelName); + assertEquals(1, channelBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); + assertEquals( + AMQPChannelBinding.builder() + .is(AMQPChannelType.ROUTING_KEY) + .exchange(AMQPChannelExchangeProperties.builder() + .name("exchange-name") + .type(AMQPChannelExchangeType.TOPIC) + .durable(false) + .autoDelete(true) + .build()) + .build(), + channelBinding.get("amqp")); } @Test - void buildChannelBinding() { + @Disabled("TODO: what to do with invalid configuration") + void buildChannelBindingWithEmptyContext() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); @@ -315,7 +391,7 @@ void buildChannelBinding() { .is(AMQPChannelType.ROUTING_KEY) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(AMQPChannelExchangeType.DIRECT) + .type(AMQPChannelExchangeType.TOPIC) .durable(true) .autoDelete(false) .build()) @@ -326,9 +402,44 @@ void buildChannelBinding() { @Test void buildOperationBinding() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); + } + + @Test + void buildOperationBindingWithExchangeContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, exchangeContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); + } + + @Test + @Disabled("TODO: what to do with invalid configuration") + void buildOperationBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -337,8 +448,7 @@ void buildOperationBinding() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals( - AMQPOperationBinding.builder().cc(List.of("routing-key")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); } @Test @@ -352,40 +462,23 @@ void buildMessageBinding() { assertEquals(new AMQPMessageBinding(), messageBinding.get("amqp")); } - private static class ClassWithBindingsAndRoutingKeyConfiguration { - + private static class ClassWithBindingConfiguration { @RabbitListener( bindings = { - @QueueBinding( - exchange = @Exchange(name = "exchange-name"), - key = "${routing-key}", - value = @Queue(name = "${queue-1}")) + @QueueBinding(exchange = @Exchange(name = "exchange-name"), value = @Queue(name = "${queue-1}")) }) private void methodWithAnnotation(String payload) {} } } @Nested - class QueueBindingsWithBeansConfiguration { - private final RabbitListenerUtilContext context; - - // Simulate a RabbitListenerUtilContext that has already been populated by existing spring beans - { - org.springframework.amqp.core.Queue queue = - new org.springframework.amqp.core.Queue("queue-1", false, true, true); - TopicExchange exchange = new TopicExchange("exchange-name", false, true); - context = RabbitListenerUtilContext.create( - List.of(queue), - List.of(exchange), - List.of(BindingBuilder.bind(queue).to(exchange).with("routing-key"))); - } + class QueueBindingWithRoutingKeyConfiguration { @Test void getChannelName() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when @@ -393,19 +486,20 @@ void getChannelName() { // then assertEquals("routing-key", channelName); + // TODO: this should take into account the context + // "queue-1_routing-key_exchange-name" } @Test void buildChannelBinding() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when Map channelBinding = - RabbitListenerUtil.buildChannelBinding(annotation, resolver, context); + RabbitListenerUtil.buildChannelBinding(annotation, resolver, queueContext); // then assertEquals(1, channelBinding.size()); @@ -415,25 +509,71 @@ void buildChannelBinding() { .is(AMQPChannelType.ROUTING_KEY) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(AMQPChannelExchangeType.TOPIC) - .durable(false) - .autoDelete(true) + .type(AMQPChannelExchangeType.DIRECT) + .durable(true) + .autoDelete(false) .build()) .build(), channelBinding.get("amqp")); } @Test - void buildOperationBinding() { + @Disabled("TODO: what to do with invalid configuration") + void buildChannelBindingWithEmptyContext() { // given - RabbitListener annotation = getAnnotation(ClassWithBindingsConfiguration.class); + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); + + // when + Map channelBinding = + RabbitListenerUtil.buildChannelBinding(annotation, resolver, emptyContext); + + // then + assertEquals(1, channelBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); + assertEquals( + AMQPChannelBinding.builder() + .is(AMQPChannelType.ROUTING_KEY) + .exchange(AMQPChannelExchangeProperties.builder() + .name("exchange-name") + .type(AMQPChannelExchangeType.DIRECT) + .durable(true) + .autoDelete(false) + .build()) + .build(), + channelBinding.get("amqp")); + } + + @Test + void buildOperationBinding() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); + + // when + Map operationBinding = + RabbitListenerUtil.buildOperationBinding(annotation, resolver, queueContext); + + // then + assertEquals(1, operationBinding.size()); + assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); + assertEquals( + AMQPOperationBinding.builder().cc(List.of("routing-key")).build(), operationBinding.get("amqp")); + } + + @Test + @Disabled("TODO: what to do with invalid configuration") + void buildOperationBindingWithEmptyContext() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); + StringValueResolver resolver = mock(StringValueResolver.class); when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when Map operationBinding = - RabbitListenerUtil.buildOperationBinding(annotation, resolver, context); + RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); // then assertEquals(1, operationBinding.size()); @@ -453,14 +593,14 @@ void buildMessageBinding() { assertEquals(new AMQPMessageBinding(), messageBinding.get("amqp")); } - private static class ClassWithBindingsConfiguration { + private static class ClassWithBindingsAndRoutingKeyConfiguration { @RabbitListener( bindings = { @QueueBinding( exchange = @Exchange(name = "exchange-name"), - value = @Queue(name = "${queue-1}"), - key = "${routing-key}"), + key = "${routing-key}", + value = @Queue(name = "${queue-1}")) }) private void methodWithAnnotation(String payload) {} } From 6814843733a3c2e851991578766a16585fdf69e3 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Wed, 31 Jul 2024 23:34:52 +0200 Subject: [PATCH 04/10] feat(amqp): update get channel name --- .../configuration/RabbitConfiguration.java | 17 ++-- .../scanners/bindings/RabbitListenerUtil.java | 64 +++++++-------- .../bindings/RabbitListenerUtilTest.java | 79 ++++++------------- 3 files changed, 70 insertions(+), 90 deletions(-) diff --git a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java index adfe68e26..38db838c4 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java +++ b/springwolf-examples/springwolf-amqp-example/src/main/java/io/github/springwolf/examples/amqp/configuration/RabbitConfiguration.java @@ -2,6 +2,7 @@ package io.github.springwolf.examples.amqp.configuration; import io.github.springwolf.examples.amqp.AmqpConstants; +import io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Exchange; @@ -32,11 +33,6 @@ public Queue anotherQueue() { return new Queue(AmqpConstants.QUEUE_ANOTHER_QUEUE, false); } - @Bean - public Queue exampleBindingsQueue() { - return new Queue(AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, false, false, true); - } - @Bean public Queue queueRead() { return new Queue(AmqpConstants.QUEUE_READ, false); @@ -52,6 +48,17 @@ public Queue multiPayloadQueue() { return new Queue(AmqpConstants.QUEUE_MULTI_PAYLOAD_QUEUE); } + /** + * Defined by @RabbitListener annotation in {@link io.github.springwolf.examples.amqp.consumers.ExampleConsumer#bindingsExample(AnotherPayloadDto)} + */ + @Bean + public Queue exampleBindingsQueue() { + return new Queue(AmqpConstants.QUEUE_EXAMPLE_BINDINGS_QUEUE, false, true, true); + } + + /** + * Defined by @RabbitListener annotation in {@link io.github.springwolf.examples.amqp.consumers.ExampleConsumer#bindingsExample(AnotherPayloadDto)} + */ @Bean public Binding exampleTopicBinding(Queue exampleBindingsQueue, Exchange exampleTopicExchange) { return BindingBuilder.bind(exampleBindingsQueue) diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 29828a6be..90bc0e3d4 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -33,13 +33,20 @@ /** * Note: bindings, queues, and queuesToDeclare are mutually exclusive - * @see RabbitListener - *

+ *

    + *
  • queues (string) point to queue beans (default exchange + routing key) + *
  • queuesToDeclare (object) will create queues on broker & matching beans (default exchange + routing key) + *
  • queueBinding (object) will create queue and exchange on broker & matching beans (exchange must match if present) + *
+ *
* How does rabbitmq work? - * 1. Producer sends a message to an exchange (default exchange if not specified) - * 2. Exchange routes the message to a queue based on the routing key (routing key = queue name if not specified) - * 3. Consumer consumes the message from the queue + *
    + *
  1. Producer sends a message to an exchange (default exchange if not specified) + *
  2. Exchange routes the message to a queue based on the routing key (routing key = queue name if not specified) + *
  3. Consumer consumes the message from the queue + *
*/ +// TODO: should this do validation and throw errors when an invalid rabbit configuration is found? @Slf4j public class RabbitListenerUtil { public static final String BINDING_NAME = "amqp"; @@ -47,35 +54,11 @@ public class RabbitListenerUtil { private static final Boolean DEFAULT_DURABLE = true; private static final Boolean DEFAULT_EXCLUSIVE = false; private static final String DEFAULT_EXCHANGE_TYPE = ExchangeTypes.DIRECT; - - // RabbitListener -> multiple for one annotation (only use first, like kafka) - // either bindings OR queues OR queuesToDeclare - // -- queueBinding (annotation) <- specific exchange - // -- queue (like queuesToDeclare) - // -- exchange (annotatoin) - // -- key (array) - // -- queues (string) (can point to queue beans) - // -- queuesToDeclare (object) (annotation instead of queue bean, default exchange, routing key = queue name) - - // binding == exchange - // - direct exchange - // - binding_key of the queue = routing key - // - default exchange - // - queue = routing key - // - topic exchange - // - binding_pattern of the queue = routing key - // - fanout exchange - // - no routing key, copy to all bindings - - // can be combined with ReplyTo and SendTo annoation (like stomp) + private static final String DEFAULT_EXCHANGE_ROUTING_KEY = "#"; public static String getChannelName(RabbitListener annotation, StringValueResolver resolver) { - // queueName_routingKey_exchange (second, third only when present) - Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) - .flatMap(binding -> Stream.concat( - Stream.of(binding.key()), // if routing key is configured, prefer it - Stream.of(binding.value().name()))); + .flatMap(binding -> channelNameFromAnnotationBindings(binding, resolver)); return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) .map(resolver::resolveStringValue) @@ -87,7 +70,7 @@ public static String getChannelName(RabbitListener annotation, StringValueResolv "No channel name was found in @RabbitListener annotation (neither in queues nor bindings property)")); } - public static String getQueueName(RabbitListener annotation, StringValueResolver resolver) { + private static String getQueueName(RabbitListener annotation, StringValueResolver resolver) { Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.of(binding.value().name())); @@ -101,6 +84,21 @@ public static String getQueueName(RabbitListener annotation, StringValueResolver "No queue name was found in @RabbitListener annotation (neither in queues nor bindings property)")); } + private static Stream channelNameFromAnnotationBindings( + QueueBinding binding, StringValueResolver resolver) { + String queueName = resolver.resolveStringValue(binding.value().name()); + String exchangeName = resolver.resolveStringValue(binding.exchange().name()); + + String[] routingKeys = binding.key(); + if (routingKeys.length == 0) { + routingKeys = List.of(DEFAULT_EXCHANGE_ROUTING_KEY).toArray(new String[0]); + } + + return Arrays.stream(routingKeys) + .map(resolver::resolveStringValue) + .map(routingKey -> String.join("_", queueName, routingKey, exchangeName)); + } + /** * * @param rabbitListenerAnnotation a RabbitListener annotation @@ -225,6 +223,7 @@ private static String getExchangeName( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { String exchangeName = Stream.of(annotation.bindings()) .map(binding -> binding.exchange().name()) + .map(resolver::resolveStringValue) .filter(StringUtils::hasText) .findFirst() .orElse(null); @@ -247,6 +246,7 @@ public static Map buildOperationBinding( return Map.of( BINDING_NAME, AMQPOperationBinding.builder() + // TODO: cc for publishing does not match listener -> remove? .cc(getRoutingKeys(annotation, resolver, context)) .build()); } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java index c30df7309..959aa7d63 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java @@ -12,6 +12,7 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import org.assertj.core.util.Sets; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -27,8 +28,9 @@ import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class RabbitListenerUtilTest { @@ -44,6 +46,23 @@ class RabbitListenerUtilTest { private final RabbitListenerUtilContext exchangeContext = RabbitListenerUtilContext.create(List.of(queue), List.of(exchange), List.of()); + StringValueResolver resolver = mock(StringValueResolver.class); + + @BeforeEach + void setUp() { + doAnswer(invocation -> { + String arg = (String) invocation.getArguments()[0]; + return switch (arg) { + case "${queue-1}" -> "queue-1"; + case "${routing-key}" -> "routing-key"; + case "${exchange-name}" -> "exchange-name"; + default -> arg; + }; + }) + .when(resolver) + .resolveStringValue(any()); + } + @Nested class QueuesConfiguration { @@ -51,8 +70,6 @@ class QueuesConfiguration { void getChannelName() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); @@ -65,8 +82,6 @@ void getChannelName() { void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -94,8 +109,6 @@ void buildChannelBinding() { void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -122,8 +135,6 @@ void buildChannelBindingWithEmptyContext() { void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -140,8 +151,6 @@ void buildOperationBinding() { void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -178,8 +187,6 @@ class QueuesToDeclareConfiguration { void getChannelName() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); @@ -193,8 +200,6 @@ void getChannelName() { void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -221,8 +226,6 @@ void buildChannelBinding() { void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -250,8 +253,6 @@ void buildChannelBindingWithEmptyContext() { void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -267,8 +268,6 @@ void buildOperationBinding() { void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -305,24 +304,18 @@ class QueueBindingsConfiguration { void getChannelName() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); // then - assertEquals("queue-1", channelName); - // TODO: this should take into account the context - // "queue-1_#_exchange-name" + assertEquals("queue-1_#_exchange-name", channelName); } @Test void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -348,8 +341,6 @@ void buildChannelBinding() { void buildChannelBindingWithExchangeContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -376,8 +367,6 @@ void buildChannelBindingWithExchangeContext() { void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -403,8 +392,6 @@ void buildChannelBindingWithEmptyContext() { void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -420,8 +407,6 @@ void buildOperationBinding() { void buildOperationBindingWithExchangeContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -438,8 +423,6 @@ void buildOperationBindingWithExchangeContext() { void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map operationBinding = @@ -465,7 +448,9 @@ void buildMessageBinding() { private static class ClassWithBindingConfiguration { @RabbitListener( bindings = { - @QueueBinding(exchange = @Exchange(name = "exchange-name"), value = @Queue(name = "${queue-1}")) + @QueueBinding( + exchange = @Exchange(name = "${exchange-name}"), + value = @Queue(name = "${queue-1}")) }) private void methodWithAnnotation(String payload) {} } @@ -478,24 +463,18 @@ class QueueBindingWithRoutingKeyConfiguration { void getChannelName() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); // then - assertEquals("routing-key", channelName); - // TODO: this should take into account the context - // "queue-1_routing-key_exchange-name" + assertEquals("queue-1_routing-key_exchange-name", channelName); } @Test void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -522,8 +501,6 @@ void buildChannelBinding() { void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when Map channelBinding = @@ -549,8 +526,6 @@ void buildChannelBindingWithEmptyContext() { void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when Map operationBinding = @@ -568,8 +543,6 @@ void buildOperationBinding() { void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); - StringValueResolver resolver = mock(StringValueResolver.class); - when(resolver.resolveStringValue("${routing-key}")).thenReturn("routing-key"); // when Map operationBinding = @@ -598,7 +571,7 @@ private static class ClassWithBindingsAndRoutingKeyConfiguration { @RabbitListener( bindings = { @QueueBinding( - exchange = @Exchange(name = "exchange-name"), + exchange = @Exchange(name = "${exchange-name}"), key = "${routing-key}", value = @Queue(name = "${queue-1}")) }) From b6c3a3b288a3d772c7d638c44d7e254c2eed5500 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Tue, 27 Aug 2024 18:47:10 +0200 Subject: [PATCH 05/10] feat(amqp): implement amqp binding proposal --- .../asyncapi/v3/model/ReferenceUtil.java | 2 +- .../scanners/bindings/BindingFactory.java | 6 +++- ...ngAnnotationClassLevelChannelsScanner.java | 2 +- ...gAnnotationMethodLevelChannelsScanner.java | 2 +- .../common/ClassLevelAnnotationScanner.java | 4 +-- .../scanners/common/MessageHelper.java | 8 ++--- ...AnnotationClassLevelOperationsScanner.java | 8 ++--- ...nnotationMethodLevelOperationsScanner.java | 6 ++-- .../scanners/bindings/AmqpBindingFactory.java | 5 +++ .../scanners/bindings/RabbitListenerUtil.java | 31 ++++++++++++++++++- .../channels/RabbitQueueBeanScanner.java | 4 +-- .../annotations/SendToCustomizer.java | 2 +- .../annotations/SendToUserCustomizer.java | 2 +- 13 files changed, 59 insertions(+), 23 deletions(-) diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java index 2768130ec..9749d4197 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java @@ -5,6 +5,6 @@ public class ReferenceUtil { private static final String FORBIDDEN_ID_CHARACTER = "/"; public static String toValidId(String name) { - return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_"); + return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_"); // TODO: easier to verify correct usage +"_id"; } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java index ceafa3d7e..5ca9bea32 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java @@ -4,12 +4,16 @@ import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding; import io.github.springwolf.asyncapi.v3.bindings.MessageBinding; import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; +import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject; import java.util.Map; public interface BindingFactory { - String getChannelName(T annotation); + default String getChannelId(T annotation) { + return ReferenceUtil.toValidId(getChannelName(annotation)); + } + String getChannelName(T annotation); Map buildChannelBinding(T annotation); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index 0ed3c95c2..806e1ed28 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -80,7 +80,7 @@ private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; String channelName = bindingFactory.getChannelName(classAnnotation); return ChannelObject.builder() - .channelId(ReferenceUtil.toValidId(channelName)) + .channelId(bindingFactory.getChannelId(classAnnotation)) .address(channelName) .bindings(chBinding) .messages(new HashMap<>(messages)) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index e1b64ce60..54a829775 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -82,7 +82,7 @@ private ChannelObject buildChannelItem(MethodAnnotation annotation, MessageObjec Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; String channelName = bindingFactory.getChannelName(annotation); return ChannelObject.builder() - .channelId(ReferenceUtil.toValidId(channelName)) + .channelId(bindingFactory.getChannelId(annotation)) .address(channelName) .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .bindings(chBinding) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java index df6aa22bb..be7e2f280 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/ClassLevelAnnotationScanner.java @@ -88,8 +88,8 @@ protected Map buildMessages( .collect(toSet()); if (messageType == MessageType.OPERATION) { - String channelName = bindingFactory.getChannelName(classAnnotation); - return toOperationsMessagesMap(channelName, messages); + String channelId = bindingFactory.getChannelId(classAnnotation); + return toOperationsMessagesMap(channelId, messages); } return toMessagesMap(messages); } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java index ccd3c37ed..0c34117ec 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java @@ -32,9 +32,9 @@ public static Map toMessagesMap(Set mes } public static Map toOperationsMessagesMap( - String channelName, Set messages) { - if (channelName == null || channelName.isBlank()) { - throw new IllegalArgumentException("channelName must not be empty"); + String channelId, Set messages) { + if (channelId == null || channelId.isBlank()) { + throw new IllegalArgumentException("channelId must not be empty"); } if (messages.isEmpty()) { @@ -46,6 +46,6 @@ public static Map toOperationsMessagesMap( .collect(Collectors.toMap( MessageObject::getMessageId, e -> MessageReference.toChannelMessage( - ReferenceUtil.toValidId(channelName), e.getMessageId()))); + channelId, e.getMessageId()))); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 59d526df1..4be4cfa56 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -71,9 +71,9 @@ private Stream> mapClassToOperation(Class compon return Stream.empty(); } - String channelName = bindingFactory.getChannelName(classAnnotation); + String channelId = bindingFactory.getChannelId(classAnnotation); String operationId = StringUtils.joinWith( - "_", ReferenceUtil.toValidId(channelName), OperationAction.RECEIVE, component.getSimpleName()); + "_", channelId, OperationAction.RECEIVE, component.getSimpleName()); Operation operation = buildOperation(classAnnotation, annotatedMethods); annotatedMethods.forEach(method -> customizers.forEach(customizer -> customizer.customize(operation, method))); @@ -89,11 +89,11 @@ private Operation buildOperation(ClassAnnotation classAnnotation, Set me private Operation buildOperation(ClassAnnotation classAnnotation, Map messages) { Map operationBinding = bindingFactory.buildOperationBinding(classAnnotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelName = bindingFactory.getChannelName(classAnnotation); + String channelId = bindingFactory.getChannelId(classAnnotation); return Operation.builder() .action(OperationAction.RECEIVE) - .channel(ChannelReference.fromChannel(ReferenceUtil.toValidId(channelName))) + .channel(ChannelReference.fromChannel(channelId)) .messages(messages.values().stream().toList()) .bindings(opBinding) .build(); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index 35ae45fad..0dc2596c0 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -70,9 +70,9 @@ private Map.Entry mapMethodToOperation(Method method) { MethodAnnotation annotation = AnnotationScannerUtil.findAnnotationOrThrow(methodAnnotationClass, method); - String channelName = bindingFactory.getChannelName(annotation); + String channelId = bindingFactory.getChannelId(annotation); String operationId = StringUtils.joinWith( - "_", ReferenceUtil.toValidId(channelName), OperationAction.RECEIVE, method.getName()); + "_", channelId, OperationAction.RECEIVE, method.getName()); PayloadSchemaObject payloadSchema = payloadMethodParameterService.extractSchema(method); SchemaObject headerSchema = headerClassExtractor.extractHeader(method, payloadSchema); @@ -87,7 +87,7 @@ private Operation buildOperation( MessageObject message = buildMessage(annotation, payloadType, headerSchema); Map operationBinding = bindingFactory.buildOperationBinding(annotation); Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation)); + String channelId = bindingFactory.getChannelId(annotation); return Operation.builder() .action(OperationAction.RECEIVE) diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java index 2df3dd760..6ef6436bb 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/AmqpBindingFactory.java @@ -29,6 +29,11 @@ public String getChannelName(RabbitListener annotation) { return RabbitListenerUtil.getChannelName(annotation, stringValueResolver); } + @Override + public String getChannelId(RabbitListener annotation) { + return RabbitListenerUtil.getChannelId(annotation, stringValueResolver); + } + @Override public Map buildChannelBinding(RabbitListener annotation) { return RabbitListenerUtil.buildChannelBinding(annotation, stringValueResolver, context); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 90bc0e3d4..015f0bb8d 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -70,6 +70,20 @@ public static String getChannelName(RabbitListener annotation, StringValueResolv "No channel name was found in @RabbitListener annotation (neither in queues nor bindings property)")); } + public static String getChannelId(RabbitListener annotation, StringValueResolver resolver) { + Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) + .flatMap(binding -> channelIdFromAnnotationBindings(binding, resolver)); + + return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) + .map(resolver::resolveStringValue) + .filter(Objects::nonNull) + .peek(queue -> log.debug("Resolved channel id: {}", queue)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException( + "No channel id was found in @RabbitListener annotation (neither in queues nor bindings property)")); + } + private static String getQueueName(RabbitListener annotation, StringValueResolver resolver) { Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.of(binding.value().name())); @@ -96,7 +110,22 @@ private static Stream channelNameFromAnnotationBindings( return Arrays.stream(routingKeys) .map(resolver::resolveStringValue) - .map(routingKey -> String.join("_", queueName, routingKey, exchangeName)); + .map(routingKey -> exchangeName); + } + + private static Stream channelIdFromAnnotationBindings( + QueueBinding binding, StringValueResolver resolver) { + String queueName = resolver.resolveStringValue(binding.value().name()); + String exchangeName = resolver.resolveStringValue(binding.exchange().name()); + + String[] routingKeys = binding.key(); + if (routingKeys.length == 0) { + routingKeys = List.of(DEFAULT_EXCHANGE_ROUTING_KEY).toArray(new String[0]); + } + + return Arrays.stream(routingKeys) + .map(resolver::resolveStringValue) + .map(routingKey -> String.join("_", queueName+"-id", routingKey, exchangeName+"-id")); } /** diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java index 79ef8726a..5aa992b6c 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java @@ -21,9 +21,7 @@ public Map scan() { return queues.stream() .map(RabbitListenerUtil::buildChannelObject) .collect(Collectors.toMap( - o -> ((AMQPChannelBinding) o.getBindings().get(RabbitListenerUtil.BINDING_NAME)) - .getQueue() - .getName(), + ChannelObject::getChannelId, c -> c, (a, b) -> a)); } diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java index 6ef62f48c..e9688a05b 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java @@ -25,7 +25,7 @@ public class SendToCustomizer implements OperationCustomizer { public void customize(Operation operation, Method method) { SendTo annotation = AnnotationScannerUtil.findAnnotation(SendTo.class, method); if (annotation != null) { - String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation)); + String channelId = bindingFactory.getChannelId(annotation); String payloadName = payloadService.extractSchema(method).name(); operation.setReply(OperationReply.builder() diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java index 326ec59c8..a789095bf 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java @@ -25,7 +25,7 @@ public class SendToUserCustomizer implements OperationCustomizer { public void customize(Operation operation, Method method) { SendToUser annotation = AnnotationScannerUtil.findAnnotation(SendToUser.class, method); if (annotation != null) { - String channelId = ReferenceUtil.toValidId(bindingFactory.getChannelName(annotation)); + String channelId = bindingFactory.getChannelId(annotation); String payloadName = payloadService.extractSchema(method).name(); operation.setReply(OperationReply.builder() From 1c48b32f59ac3409a462fececddb3c252297f139 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Wed, 28 Aug 2024 21:08:42 +0200 Subject: [PATCH 06/10] feat(amqp): use 0.4.0 binding --- .../v3/bindings/amqp/AMQPChannelBinding.java | 14 +++- .../v3/bindings/amqp/AMQPMessageBinding.java | 2 +- .../bindings/amqp/AMQPOperationBinding.java | 2 +- .../asyncapi/v3/model/ReferenceUtil.java | 2 + .../v3/bindings/amqp/amqp-channel-queue.yaml | 2 +- .../bindings/amqp/amqp-channel-routing.yaml | 2 +- .../v3/bindings/amqp/amqp-message.yaml | 2 +- .../v3/bindings/amqp/amqp-operation.yaml | 2 +- .../scanners/bindings/BindingFactory.java | 3 +- ...ngAnnotationClassLevelChannelsScanner.java | 4 +- ...gAnnotationMethodLevelChannelsScanner.java | 4 +- .../scanners/common/MessageHelper.java | 7 +- ...AnnotationClassLevelOperationsScanner.java | 4 +- ...nnotationMethodLevelOperationsScanner.java | 4 +- ...notationClassLevelChannelsScannerTest.java | 2 + ...otationMethodLevelChannelsScannerTest.java | 1 + ...tationClassLevelOperationsScannerTest.java | 1 + ...ationMethodLevelOperationsScannerTest.java | 1 + .../amqp/SpringContextIntegrationTest.java | 2 +- .../scanners/bindings/RabbitListenerUtil.java | 66 +++++++++---------- .../channels/RabbitQueueBeanScanner.java | 6 +- .../bindings/RabbitListenerUtilTest.java | 46 ++++++++++--- .../channels/RabbitQueueBeanScannerTest.java | 4 +- .../annotations/SendToCustomizer.java | 1 - .../annotations/SendToUserCustomizer.java | 1 - .../annotations/SendToCustomizerTest.java | 1 + .../annotations/SendToUserCustomizerTest.java | 1 + 27 files changed, 113 insertions(+), 74 deletions(-) diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPChannelBinding.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPChannelBinding.java index 0c17d3e2f..a5656713f 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPChannelBinding.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPChannelBinding.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; @@ -26,6 +27,17 @@ public class AMQPChannelBinding extends ChannelBinding { @JsonProperty(value = "is", required = true, defaultValue = "routingKey") private AMQPChannelType is = AMQPChannelType.ROUTING_KEY; + /** + * When is=routingKey, this defines the actual routing pattern to route the message from the exchange to the queue. + */ + @JsonProperty("name") + private String name; + + /** + * When is=routingKey, this defines the target queue after routing the message (essentially the binding). + */ + private ChannelReference channel; + @JsonProperty("exchange") private AMQPChannelExchangeProperties exchange; @@ -37,5 +49,5 @@ public class AMQPChannelBinding extends ChannelBinding { @Builder.Default @JsonProperty(value = "bindingVersion") - private final String bindingVersion = "0.3.0"; + private final String bindingVersion = "0.4.0"; } diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPMessageBinding.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPMessageBinding.java index 4229e2390..149e4f61e 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPMessageBinding.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPMessageBinding.java @@ -35,5 +35,5 @@ public class AMQPMessageBinding extends MessageBinding { */ @Builder.Default @JsonProperty(value = "bindingVersion") - private final String bindingVersion = "0.3.0"; + private final String bindingVersion = "0.4.0"; } diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPOperationBinding.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPOperationBinding.java index 416e388ac..4e1c3dbcf 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPOperationBinding.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/bindings/amqp/AMQPOperationBinding.java @@ -109,5 +109,5 @@ public class AMQPOperationBinding extends OperationBinding { */ @Builder.Default @JsonProperty("bindingVersion") - private String bindingVersion = "0.3.0"; + private String bindingVersion = "0.4.0"; } diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java index 9749d4197..974688f1a 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java @@ -2,6 +2,8 @@ package io.github.springwolf.asyncapi.v3.model; public class ReferenceUtil { + public static final String ID_POSTFIX = "_id"; + private static final String FORBIDDEN_ID_CHARACTER = "/"; public static String toValidId(String name) { diff --git a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-queue.yaml b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-queue.yaml index 922074113..3c5ff08d4 100644 --- a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-queue.yaml +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-queue.yaml @@ -10,4 +10,4 @@ channels: exclusive: true autoDelete: false vhost: / - bindingVersion: 0.3.0 \ No newline at end of file + bindingVersion: 0.4.0 \ No newline at end of file diff --git a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml index 0bcaed1fb..154429b94 100644 --- a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml @@ -10,4 +10,4 @@ channels: durable: true autoDelete: false vhost: / - bindingVersion: 0.3.0 \ No newline at end of file + bindingVersion: 0.4.0 \ No newline at end of file diff --git a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-message.yaml b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-message.yaml index 67b76119c..aa0cbc732 100644 --- a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-message.yaml +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-message.yaml @@ -7,4 +7,4 @@ channels: amqp: contentEncoding: gzip messageType: 'user.signup' - bindingVersion: 0.3.0 \ No newline at end of file + bindingVersion: 0.4.0 \ No newline at end of file diff --git a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-operation.yaml b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-operation.yaml index 001049f1c..aee0d69d2 100644 --- a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-operation.yaml +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-operation.yaml @@ -13,4 +13,4 @@ operations: bcc: ['external.audit'] timestamp: true ack: false - bindingVersion: 0.3.0 \ No newline at end of file + bindingVersion: 0.4.0 \ No newline at end of file diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java index 5ca9bea32..5f89b7e8a 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/bindings/BindingFactory.java @@ -13,7 +13,8 @@ public interface BindingFactory { default String getChannelId(T annotation) { return ReferenceUtil.toValidId(getChannelName(annotation)); } - String getChannelName(T annotation); + + String getChannelName(T annotation); Map buildChannelBinding(T annotation); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java index 806e1ed28..6c8fd93d4 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScanner.java @@ -2,7 +2,6 @@ package io.github.springwolf.core.asyncapi.scanners.channels.annotations; import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.core.asyncapi.components.ComponentsService; @@ -79,8 +78,9 @@ private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Map channelBinding = bindingFactory.buildChannelBinding(classAnnotation); Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; String channelName = bindingFactory.getChannelName(classAnnotation); + String channelId = bindingFactory.getChannelId(classAnnotation); return ChannelObject.builder() - .channelId(bindingFactory.getChannelId(classAnnotation)) + .channelId(channelId) .address(channelName) .bindings(chBinding) .messages(new HashMap<>(messages)) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java index 54a829775..4a2c7e527 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScanner.java @@ -2,7 +2,6 @@ package io.github.springwolf.core.asyncapi.scanners.channels.annotations; import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; @@ -81,8 +80,9 @@ private ChannelObject buildChannelItem(MethodAnnotation annotation, MessageObjec Map channelBinding = bindingFactory.buildChannelBinding(annotation); Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; String channelName = bindingFactory.getChannelName(annotation); + String channelId = bindingFactory.getChannelId(annotation); return ChannelObject.builder() - .channelId(bindingFactory.getChannelId(annotation)) + .channelId(channelId) .address(channelName) .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .bindings(chBinding) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java index 0c34117ec..fd258d3c2 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/MessageHelper.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.core.asyncapi.scanners.common; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import lombok.extern.slf4j.Slf4j; @@ -31,8 +30,7 @@ public static Map toMessagesMap(Set mes .stream().collect(Collectors.toMap(MessageObject::getMessageId, MessageReference::toComponentMessage)); } - public static Map toOperationsMessagesMap( - String channelId, Set messages) { + public static Map toOperationsMessagesMap(String channelId, Set messages) { if (channelId == null || channelId.isBlank()) { throw new IllegalArgumentException("channelId must not be empty"); } @@ -45,7 +43,6 @@ public static Map toOperationsMessagesMap( .stream() .collect(Collectors.toMap( MessageObject::getMessageId, - e -> MessageReference.toChannelMessage( - channelId, e.getMessageId()))); + e -> MessageReference.toChannelMessage(channelId, e.getMessageId()))); } } diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java index 4be4cfa56..44363de7f 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScanner.java @@ -2,7 +2,6 @@ package io.github.springwolf.core.asyncapi.scanners.operations.annotations; import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.operation.Operation; @@ -72,8 +71,7 @@ private Stream> mapClassToOperation(Class compon } String channelId = bindingFactory.getChannelId(classAnnotation); - String operationId = StringUtils.joinWith( - "_", channelId, OperationAction.RECEIVE, component.getSimpleName()); + String operationId = StringUtils.joinWith("_", channelId, OperationAction.RECEIVE, component.getSimpleName()); Operation operation = buildOperation(classAnnotation, annotatedMethods); annotatedMethods.forEach(method -> customizers.forEach(customizer -> customizer.customize(operation, method))); diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java index 0dc2596c0..7e9169303 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScanner.java @@ -2,7 +2,6 @@ package io.github.springwolf.core.asyncapi.scanners.operations.annotations; import io.github.springwolf.asyncapi.v3.bindings.OperationBinding; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; @@ -71,8 +70,7 @@ private Map.Entry mapMethodToOperation(Method method) { MethodAnnotation annotation = AnnotationScannerUtil.findAnnotationOrThrow(methodAnnotationClass, method); String channelId = bindingFactory.getChannelId(annotation); - String operationId = StringUtils.joinWith( - "_", channelId, OperationAction.RECEIVE, method.getName()); + String operationId = StringUtils.joinWith("_", channelId, OperationAction.RECEIVE, method.getName()); PayloadSchemaObject payloadSchema = payloadMethodParameterService.extractSchema(method); SchemaObject headerSchema = headerClassExtractor.extractHeader(method, payloadSchema); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java index 4d2030f7f..811a7703d 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationClassLevelChannelsScannerTest.java @@ -7,6 +7,7 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageHeaders; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; @@ -67,6 +68,7 @@ class SpringAnnotationClassLevelChannelsScannerTest { @BeforeEach void setUp() { // when + when(bindingFactory.getChannelId(any())).thenReturn(ReferenceUtil.toValidId(CHANNEL)); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL); doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java index 56cce2407..a9662e0bd 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/SpringAnnotationMethodLevelChannelsScannerTest.java @@ -73,6 +73,7 @@ class SpringAnnotationMethodLevelChannelsScannerTest { @BeforeEach void setUp() throws NoSuchMethodException { // when + when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL); doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java index 0e3f0c9dc..4c6ee7f27 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationClassLevelOperationsScannerTest.java @@ -68,6 +68,7 @@ class SpringAnnotationClassLevelOperationsScannerTest { @BeforeEach void setUp() { // when + when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java index faa2bbef3..29ca70945 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/SpringAnnotationMethodLevelOperationsScannerTest.java @@ -67,6 +67,7 @@ class SpringAnnotationMethodLevelOperationsScannerTest { @BeforeEach void setUp() { // when + when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java index f38bccbf6..6375ed490 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/springwolf/examples/amqp/SpringContextIntegrationTest.java @@ -44,7 +44,7 @@ void testContextWithApplicationProperties() { @Test void testAllChannelsAreFound() { - assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(11); + assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(12); } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 015f0bb8d..6c8588860 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -13,6 +13,7 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; @@ -24,13 +25,14 @@ import org.springframework.util.StringValueResolver; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import static io.github.springwolf.asyncapi.v3.model.ReferenceUtil.ID_POSTFIX; + /** * Note: bindings, queues, and queuesToDeclare are mutually exclusive *
    @@ -71,10 +73,10 @@ public static String getChannelName(RabbitListener annotation, StringValueResolv } public static String getChannelId(RabbitListener annotation, StringValueResolver resolver) { - Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) + Stream annotationBindingChannelIds = Arrays.stream(annotation.bindings()) .flatMap(binding -> channelIdFromAnnotationBindings(binding, resolver)); - return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) + return Stream.concat(streamQueueNames(annotation).map(name -> name + ID_POSTFIX), annotationBindingChannelIds) .map(resolver::resolveStringValue) .filter(Objects::nonNull) .peek(queue -> log.debug("Resolved channel id: {}", queue)) @@ -108,13 +110,19 @@ private static Stream channelNameFromAnnotationBindings( routingKeys = List.of(DEFAULT_EXCHANGE_ROUTING_KEY).toArray(new String[0]); } - return Arrays.stream(routingKeys) + return Arrays.stream(routingKeys).map(resolver::resolveStringValue).map(routingKey -> exchangeName); + } + + private static String exchangeTargetChannelIdFromAnnotationBindings( + RabbitListener annotation, StringValueResolver resolver) { + return Arrays.stream(annotation.bindings()) + .map(binding -> binding.value().name() + "-id") .map(resolver::resolveStringValue) - .map(routingKey -> exchangeName); + .findFirst() + .orElse(null); } - private static Stream channelIdFromAnnotationBindings( - QueueBinding binding, StringValueResolver resolver) { + private static Stream channelIdFromAnnotationBindings(QueueBinding binding, StringValueResolver resolver) { String queueName = resolver.resolveStringValue(binding.value().name()); String exchangeName = resolver.resolveStringValue(binding.exchange().name()); @@ -125,7 +133,7 @@ private static Stream channelIdFromAnnotationBindings( return Arrays.stream(routingKeys) .map(resolver::resolveStringValue) - .map(routingKey -> String.join("_", queueName+"-id", routingKey, exchangeName+"-id")); + .map(routingKey -> String.join("_", queueName + "-id", routingKey, exchangeName + "-id")); } /** @@ -155,6 +163,9 @@ public static Map buildChannelBinding( channelBinding.queue(buildQueueProperties(annotation, resolver, context)); } else { channelBinding.is(AMQPChannelType.ROUTING_KEY); + channelBinding.name(getRoutingPattern(annotation, resolver, context)); + channelBinding.channel( + ChannelReference.fromChannel(exchangeTargetChannelIdFromAnnotationBindings(annotation, resolver))); channelBinding.exchange(buildExchangeProperties(annotation, exchangeName, context)); } @@ -225,7 +236,7 @@ private static AMQPChannelQueueProperties buildQueueProperties( public static ChannelObject buildChannelObject(org.springframework.amqp.core.Queue queue) { return ChannelObject.builder() - .channelId(ReferenceUtil.toValidId(queue.getName())) + .channelId(ReferenceUtil.toValidId(queue.getName()) + ID_POSTFIX) .address(queue.getName()) .bindings(Map.of( BINDING_NAME, @@ -272,44 +283,33 @@ private static String getExchangeName( public static Map buildOperationBinding( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { - return Map.of( - BINDING_NAME, - AMQPOperationBinding.builder() - // TODO: cc for publishing does not match listener -> remove? - .cc(getRoutingKeys(annotation, resolver, context)) - .build()); + return Map.of(BINDING_NAME, AMQPOperationBinding.builder().build()); } - private static List getRoutingKeys( + private static String getRoutingPattern( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { - List routingKeys = Stream.of(annotation.bindings()) - .map(binding -> { - if (binding.key().length == 0) { - // The routing key is taken from the binding. As the key field in the @QueueBinding can be an - // empty array, it is set as an empty String in that case. - return Collections.singletonList(""); - } - - return Arrays.stream(binding.key()) - .map(resolver::resolveStringValue) - .toList(); - }) + String routingKey = Stream.of(annotation.bindings()) + .flatMap(binding -> Arrays.stream(binding.key()).map(resolver::resolveStringValue)) .findFirst() .orElse(null); Binding binding = context.bindingMap().get(getChannelName(annotation, resolver)); - if (routingKeys == null && binding != null) { - routingKeys = Collections.singletonList(binding.getRoutingKey()); + if (routingKey == null && binding != null) { + routingKey = binding.getRoutingKey(); } // when there is no binding for the queue present at all, it uses the fact that // RabbitMQ automatically binds default exchange to a queue with queue's name as a routing key. String exchangeName = getExchangeName(annotation, resolver, context); - if (routingKeys == null && exchangeName.isEmpty()) { - routingKeys = Collections.singletonList(getQueueName(annotation, resolver)); + if (routingKey == null && exchangeName.isEmpty()) { + routingKey = getQueueName(annotation, resolver); + } + + if (routingKey == null) { + routingKey = DEFAULT_EXCHANGE_ROUTING_KEY; } - return routingKeys; + return routingKey; } public static Map buildMessageBinding() { diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java index 5aa992b6c..b7bb488e2 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScanner.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.plugins.amqp.asyncapi.scanners.channels; -import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject; import io.github.springwolf.core.asyncapi.scanners.ChannelsScanner; import io.github.springwolf.plugins.amqp.asyncapi.scanners.bindings.RabbitListenerUtil; @@ -20,9 +19,6 @@ public class RabbitQueueBeanScanner implements ChannelsScanner { public Map scan() { return queues.stream() .map(RabbitListenerUtil::buildChannelObject) - .collect(Collectors.toMap( - ChannelObject::getChannelId, - c -> c, - (a, b) -> a)); + .collect(Collectors.toMap(ChannelObject::getChannelId, c -> c, (a, b) -> a)); } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java index 959aa7d63..a3b7323c2 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java @@ -11,6 +11,7 @@ import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import io.github.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import org.assertj.core.util.Sets; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -54,6 +55,7 @@ void setUp() { String arg = (String) invocation.getArguments()[0]; return switch (arg) { case "${queue-1}" -> "queue-1"; + case "${queue-1}-id" -> "queue-1-id"; case "${routing-key}" -> "routing-key"; case "${exchange-name}" -> "exchange-name"; default -> arg; @@ -143,7 +145,7 @@ void buildOperationBinding() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -276,7 +278,7 @@ void buildOperationBindingWithEmptyContext() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -300,6 +302,18 @@ private void methodWithAnnotation(String payload) {} @Nested class QueueBindingsConfiguration { + @Test + void getChannelId() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); + + // when + String channelName = RabbitListenerUtil.getChannelId(annotation, resolver); + + // then + assertEquals("queue-1-id_#_exchange-name-id", channelName); + } + @Test void getChannelName() { // given @@ -309,7 +323,7 @@ void getChannelName() { String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); // then - assertEquals("queue-1_#_exchange-name", channelName); + assertEquals("exchange-name", channelName); } @Test @@ -327,6 +341,8 @@ void buildChannelBinding() { assertEquals( AMQPChannelBinding.builder() .is(AMQPChannelType.ROUTING_KEY) + .name("#") + .channel(ChannelReference.fromChannel("queue-1-id")) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") .type(AMQPChannelExchangeType.DIRECT) @@ -352,6 +368,8 @@ void buildChannelBindingWithExchangeContext() { assertEquals( AMQPChannelBinding.builder() .is(AMQPChannelType.ROUTING_KEY) + .name("#") + .channel(ChannelReference.fromChannel("queue-1-id")) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") .type(AMQPChannelExchangeType.TOPIC) @@ -400,7 +418,7 @@ void buildOperationBinding() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -415,7 +433,7 @@ void buildOperationBindingWithExchangeContext() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -458,6 +476,17 @@ private void methodWithAnnotation(String payload) {} @Nested class QueueBindingWithRoutingKeyConfiguration { + @Test + void getChannelId() { + // given + RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); + + // when + String channelName = RabbitListenerUtil.getChannelId(annotation, resolver); + + // then + assertEquals("queue-1-id_routing-key_exchange-name-id", channelName); + } @Test void getChannelName() { @@ -468,7 +497,7 @@ void getChannelName() { String channelName = RabbitListenerUtil.getChannelName(annotation, resolver); // then - assertEquals("queue-1_routing-key_exchange-name", channelName); + assertEquals("exchange-name", channelName); } @Test @@ -486,6 +515,8 @@ void buildChannelBinding() { assertEquals( AMQPChannelBinding.builder() .is(AMQPChannelType.ROUTING_KEY) + .name("routing-key") + .channel(ChannelReference.fromChannel("queue-1-id")) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") .type(AMQPChannelExchangeType.DIRECT) @@ -534,8 +565,7 @@ void buildOperationBinding() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals( - AMQPOperationBinding.builder().cc(List.of("routing-key")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java index d12d8244b..44e7d8919 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/channels/RabbitQueueBeanScannerTest.java @@ -27,9 +27,9 @@ void scan() { // then assertThat(result) .isEqualTo(Map.of( - "name", + "name_id", ChannelObject.builder() - .channelId("name") + .channelId("name_id") .address("name") .bindings(Map.of( "amqp", diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java index e9688a05b..3dc5ad430 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizer.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.operation.Operation; diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java index a789095bf..a8cefded7 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/main/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizer.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.springwolf.plugins.stomp.asyncapi.scanners.operation.annotations; -import io.github.springwolf.asyncapi.v3.model.ReferenceUtil; import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.springwolf.asyncapi.v3.model.operation.Operation; diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java index 98af9a0ea..b983fe908 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToCustomizerTest.java @@ -31,6 +31,7 @@ class SendToCustomizerTest { void customize() throws NoSuchMethodException { // given Operation operation = new Operation(); + when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); when(payloadService.extractSchema(any())).thenReturn(new PayloadSchemaObject(MESSAGE_ID, null)); diff --git a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java index 960973648..cc80f80ec 100644 --- a/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java +++ b/springwolf-plugins/springwolf-stomp-plugin/src/test/java/io/github/springwolf/plugins/stomp/asyncapi/scanners/operation/annotations/SendToUserCustomizerTest.java @@ -31,6 +31,7 @@ class SendToUserCustomizerTest { void customize() throws NoSuchMethodException { // given Operation operation = new Operation(); + when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID); when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID); when(payloadService.extractSchema(any())).thenReturn(new PayloadSchemaObject(MESSAGE_ID, null)); From 9af5f194bef0d87de87f32092756dbb4c45dbadd Mon Sep 17 00:00:00 2001 From: Timon Back Date: Wed, 28 Aug 2024 21:32:43 +0200 Subject: [PATCH 07/10] feat(core): add _id postfix to channelId --- .../asyncapi/v3/model/ReferenceUtil.java | 2 +- .../asyncapi/v3/model/ReferenceUtilTest.java | 2 +- .../AsyncAnnotationChannelsScannerTest.java | 11 +- .../AsyncAnnotationOperationsScannerTest.java | 65 +++++----- .../AsyncApiDocumentIntegrationTest.java | 8 +- .../src/test/resources/asyncapi.json | 10 +- .../src/test/resources/asyncapi.json | 40 +++--- .../src/test/resources/asyncapi.json | 24 ++-- .../src/test/resources/asyncapi.json | 122 +++++++++--------- .../src/test/resources/asyncapi.yaml | 120 ++++++++--------- .../src/test/resources/asyncapi.json | 28 ++-- .../src/test/resources/asyncapi.json | 24 ++-- .../src/test/resources/asyncapi.json | 46 +++---- .../scanners/bindings/RabbitListenerUtil.java | 6 +- ...unctionChannelsScannerIntegrationTest.java | 77 +++++------ 15 files changed, 295 insertions(+), 290 deletions(-) diff --git a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java index 974688f1a..09dffaf10 100644 --- a/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java +++ b/springwolf-asyncapi/src/main/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtil.java @@ -7,6 +7,6 @@ public class ReferenceUtil { private static final String FORBIDDEN_ID_CHARACTER = "/"; public static String toValidId(String name) { - return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_"); // TODO: easier to verify correct usage +"_id"; + return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_") + ID_POSTFIX; } } diff --git a/springwolf-asyncapi/src/test/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtilTest.java b/springwolf-asyncapi/src/test/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtilTest.java index f795f143e..f1aabdfb6 100644 --- a/springwolf-asyncapi/src/test/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtilTest.java +++ b/springwolf-asyncapi/src/test/java/io/github/springwolf/asyncapi/v3/model/ReferenceUtilTest.java @@ -10,6 +10,6 @@ class ReferenceUtilTest { void shouldCorrectIllegalCharacter() { String name = "users/{userId}"; - assertThat(ReferenceUtil.toValidId(name)).isEqualTo("users_{userId}"); + assertThat(ReferenceUtil.toValidId(name)).isEqualTo("users_{userId}_id"); } } diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java index d8bf2c0d5..a18fbfb70 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/channels/annotations/AsyncAnnotationChannelsScannerTest.java @@ -53,6 +53,7 @@ import java.util.Map; import java.util.Set; +import static io.github.springwolf.asyncapi.v3.model.ReferenceUtil.ID_POSTFIX; import static java.util.Collections.EMPTY_MAP; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; @@ -187,7 +188,7 @@ void scan_componentHasListenerMethodWithUnknownServer() { assertThatThrownBy(channelScanner::scan) .isInstanceOf(IllegalArgumentException.class) .hasMessage( - "Operation 'test_channel_send' defines unknown server ref 'server3'. This AsyncApi defines these server(s): [server1, server2]"); + "Operation 'test_channel_id_send' defines unknown server ref 'server3'. This AsyncApi defines these server(s): [server1, server2]"); } @Test @@ -249,14 +250,14 @@ void scan_componentHasMultipleListenerAnnotations() { .build(); ChannelObject expectedChannel1 = ChannelObject.builder() - .channelId("test-channel-1") + .channelId("test-channel-1" + ID_POSTFIX) .address("test-channel-1") .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .bindings(null) .build(); ChannelObject expectedChannel2 = ChannelObject.builder() - .channelId("test-channel-2") + .channelId("test-channel-2" + ID_POSTFIX) .address("test-channel-2") .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .bindings(null) @@ -264,8 +265,8 @@ void scan_componentHasMultipleListenerAnnotations() { assertThat(actualChannels) .containsExactlyInAnyOrderEntriesOf(Map.of( - "test-channel-1", expectedChannel1, - "test-channel-2", expectedChannel2)); + "test-channel-1_id", expectedChannel1, + "test-channel-2_id", expectedChannel2)); } @Test diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java index ac409a91b..9ae45d3e0 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/operations/annotations/AsyncAnnotationOperationsScannerTest.java @@ -52,6 +52,7 @@ import java.util.Map; import java.util.Set; +import static io.github.springwolf.asyncapi.v3.model.ReferenceUtil.ID_POSTFIX; import static java.util.Collections.EMPTY_MAP; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; @@ -169,16 +170,16 @@ void scan_componentOperationHasListenerMethod() { .build(); Operation expectedOperation = Operation.builder() - .title("test-channel_send") + .title("test-channel_id_send") .action(OperationAction.SEND) .description("Auto-generated description") - .channel(ChannelReference.fromChannel("test-channel")) - .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .channel(ChannelReference.fromChannel("test-channel" + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage("test-channel" + ID_POSTFIX, message))) .bindings(EMPTY_MAP) .build(); assertThat(actualOperations) - .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + .containsExactly(Map.entry("test-channel_id_send_methodWithAnnotation", expectedOperation)); } @Test @@ -206,15 +207,15 @@ void scan_componentHasListenerMethodWithAllAttributes() { Operation expectedOperation = Operation.builder() .action(OperationAction.SEND) - .title("test-channel_send") - .channel(ChannelReference.fromChannel("test-channel")) + .title("test-channel_id_send") + .channel(ChannelReference.fromChannel("test-channel" + ID_POSTFIX)) .description("description") .bindings(Map.of(TestOperationBindingProcessor.TYPE, TestOperationBindingProcessor.BINDING)) - .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel" + ID_POSTFIX, message))) .build(); assertThat(actualOperations) - .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + .containsExactly(Map.entry("test-channel_id_send_methodWithAnnotation", expectedOperation)); } @Test @@ -243,26 +244,26 @@ void scan_componentHasMultipleListenerAnnotations() { Operation expectedOperation1 = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("test-channel-1")) + .channel(ChannelReference.fromChannel("test-channel-1" + ID_POSTFIX)) .description("test-channel-1-description") - .title("test-channel-1_send") + .title("test-channel-1_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("test-channel-1", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel-1" + ID_POSTFIX, message))) .build(); Operation expectedOperation2 = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("test-channel-2")) + .channel(ChannelReference.fromChannel("test-channel-2" + ID_POSTFIX)) .description("test-channel-2-description") - .title("test-channel-2_send") + .title("test-channel-2_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("test-channel-2", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel-2" + ID_POSTFIX, message))) .build(); assertThat(actualOperations) .containsExactlyInAnyOrderEntriesOf(Map.of( - "test-channel-1_send_methodWithMultipleAnnotation", expectedOperation1, - "test-channel-2_send_methodWithMultipleAnnotation", expectedOperation2)); + "test-channel-1_id_send_methodWithMultipleAnnotation", expectedOperation1, + "test-channel-2_id_send_methodWithMultipleAnnotation", expectedOperation2)); } @Test @@ -291,15 +292,15 @@ void scan_componentHasAsyncMethodAnnotation() { Operation expectedOperation = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("test-channel")) + .channel(ChannelReference.fromChannel("test-channel" + ID_POSTFIX)) .description("test channel operation description") - .title("test-channel_send") + .title("test-channel_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel" + ID_POSTFIX, message))) .build(); assertThat(actualOperations) - .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + .containsExactly(Map.entry("test-channel_id_send_methodWithAnnotation", expectedOperation)); } @Test @@ -328,15 +329,15 @@ void scan_componentHasAsyncMethodAnnotationInAbstractClass() { Operation expectedOperation = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("abstract-test-channel")) + .channel(ChannelReference.fromChannel("abstract-test-channel" + ID_POSTFIX)) .description("test abstract channel operation description") - .title("abstract-test-channel_send") + .title("abstract-test-channel_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("abstract-test-channel", message))) + .messages(List.of(MessageReference.toChannelMessage("abstract-test-channel" + ID_POSTFIX, message))) .build(); assertThat(actualOperations) - .containsExactly(Map.entry("abstract-test-channel_send_methodWithAnnotation", expectedOperation)); + .containsExactly(Map.entry("abstract-test-channel_id_send_methodWithAnnotation", expectedOperation)); } @Test @@ -471,11 +472,11 @@ void scan_componentHasOnlyDeclaredMethods(Class clazz) { Operation expectedOperation = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("test-channel")) + .channel(ChannelReference.fromChannel("test-channel" + ID_POSTFIX)) .description("test channel operation description") - .title("test-channel_send") + .title("test-channel_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel" + ID_POSTFIX, message))) .build(); ChannelObject expectedChannel = ChannelObject.builder() @@ -484,7 +485,7 @@ void scan_componentHasOnlyDeclaredMethods(Class clazz) { .build(); assertThat(actualOperations) - .containsExactly(Map.entry("test-channel_send_methodFromInterface", expectedOperation)); + .containsExactly(Map.entry("test-channel_id_send_methodFromInterface", expectedOperation)); } private static class ClassImplementingInterface implements ClassInterface { @@ -546,11 +547,11 @@ void scan_componentHasListenerMethodWithMetaAnnotation() { Operation expectedOperation = Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel("test-channel")) + .channel(ChannelReference.fromChannel("test-channel" + ID_POSTFIX)) .description("test channel operation description") - .title("test-channel_send") + .title("test-channel_id_send") .bindings(EMPTY_MAP) - .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .messages(List.of(MessageReference.toChannelMessage("test-channel" + ID_POSTFIX, message))) .build(); ChannelObject expectedChannel = ChannelObject.builder() @@ -559,7 +560,7 @@ void scan_componentHasListenerMethodWithMetaAnnotation() { .build(); assertThat(actualOperations) - .containsExactly(Map.entry("test-channel_send_methodFromInterface", expectedOperation)); + .containsExactly(Map.entry("test-channel_id_send_methodFromInterface", expectedOperation)); } public static class ClassWithMetaAnnotation { diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java index 471ac3253..0cfd4518c 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java @@ -38,8 +38,8 @@ void asyncListenerAnnotationIsFound() { AsyncAPI asyncAPI = asyncApiService.getAsyncAPI(); assertThat(asyncAPI).isNotNull(); - assertThat(asyncAPI.getChannels()).containsOnlyKeys("listener-channel"); - assertThat(asyncAPI.getOperations()).containsOnlyKeys("listener-channel_receive_listen"); + assertThat(asyncAPI.getChannels()).containsOnlyKeys("listener-channel_id"); + assertThat(asyncAPI.getOperations()).containsOnlyKeys("listener-channel_id_receive_listen"); assertThat(asyncAPI.getComponents().getMessages()).containsOnlyKeys("java.lang.String"); assertThat(asyncAPI.getComponents().getSchemas()) .containsOnlyKeys("HeadersNotDocumented", "java.lang.String"); @@ -62,8 +62,8 @@ void asyncPublisherAnnotationIsFound() { AsyncAPI asyncAPI = asyncApiService.getAsyncAPI(); assertThat(asyncAPI).isNotNull(); - assertThat(asyncAPI.getChannels()).containsOnlyKeys("publisher-channel"); - assertThat(asyncAPI.getOperations()).containsOnlyKeys("publisher-channel_send_publish"); + assertThat(asyncAPI.getChannels()).containsOnlyKeys("publisher-channel_id"); + assertThat(asyncAPI.getOperations()).containsOnlyKeys("publisher-channel_id_send_publish"); assertThat(asyncAPI.getComponents().getMessages()).containsOnlyKeys("java.lang.String"); assertThat(asyncAPI.getComponents().getSchemas()) .containsOnlyKeys("HeadersNotDocumented", "java.lang.String"); diff --git a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json index 8deb3416f..bb02346b5 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json @@ -107,7 +107,7 @@ } } }, - "example-topic-exchange": { + "example-topic-exchange_id": { "address": "example-topic-exchange", "messages": { "io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto": { @@ -490,12 +490,12 @@ } ] }, - "example-topic-exchange_send_sendMessage": { + "example-topic-exchange_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/example-topic-exchange" + "$ref": "#/channels/example-topic-exchange_id" }, - "title": "example-topic-exchange_send", + "title": "example-topic-exchange_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "amqp": { @@ -511,7 +511,7 @@ }, "messages": [ { - "$ref": "#/channels/example-topic-exchange/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + "$ref": "#/channels/example-topic-exchange_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" } ] }, diff --git a/springwolf-examples/springwolf-cloud-stream-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-cloud-stream-example/src/test/resources/asyncapi.json index 9d6a24de9..10e628128 100644 --- a/springwolf-examples/springwolf-cloud-stream-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-cloud-stream-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "another-topic": { + "another-topic_id": { "address": "another-topic", "messages": { "io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto": { @@ -33,7 +33,7 @@ "kafka": { } } }, - "consumer-class-topic": { + "consumer-class-topic_id": { "address": "consumer-class-topic", "messages": { "io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto": { @@ -44,7 +44,7 @@ "kafka": { } } }, - "consumer-topic": { + "consumer-topic_id": { "address": "consumer-topic", "messages": { "io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto": { @@ -55,7 +55,7 @@ "kafka": { } } }, - "example-topic": { + "example-topic_id": { "address": "example-topic", "messages": { "io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto": { @@ -66,7 +66,7 @@ "kafka": { } } }, - "google-pubsub-topic": { + "google-pubsub-topic_id": { "address": "google-pubsub-topic", "messages": { "io.github.springwolf.examples.cloudstream.dtos.GooglePubSubPayloadDto": { @@ -263,10 +263,10 @@ } }, "operations": { - "another-topic_subscribe_process": { + "another-topic_id_subscribe_process": { "action": "send", "channel": { - "$ref": "#/channels/another-topic" + "$ref": "#/channels/another-topic_id" }, "description": "Auto-generated description", "bindings": { @@ -274,14 +274,14 @@ }, "messages": [ { - "$ref": "#/channels/another-topic/messages/io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-topic_id/messages/io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto" } ] }, - "consumer-class-topic_publish_ConsumerClass": { + "consumer-class-topic_id_publish_ConsumerClass": { "action": "receive", "channel": { - "$ref": "#/channels/consumer-class-topic" + "$ref": "#/channels/consumer-class-topic_id" }, "description": "Auto-generated description", "bindings": { @@ -289,14 +289,14 @@ }, "messages": [ { - "$ref": "#/channels/consumer-class-topic/messages/io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto" + "$ref": "#/channels/consumer-class-topic_id/messages/io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto" } ] }, - "consumer-topic_publish_consumerMethod": { + "consumer-topic_id_publish_consumerMethod": { "action": "receive", "channel": { - "$ref": "#/channels/consumer-topic" + "$ref": "#/channels/consumer-topic_id" }, "description": "Auto-generated description", "bindings": { @@ -304,14 +304,14 @@ }, "messages": [ { - "$ref": "#/channels/consumer-topic/messages/io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto" + "$ref": "#/channels/consumer-topic_id/messages/io.github.springwolf.examples.cloudstream.dtos.AnotherPayloadDto" } ] }, - "example-topic_publish_process": { + "example-topic_id_publish_process": { "action": "receive", "channel": { - "$ref": "#/channels/example-topic" + "$ref": "#/channels/example-topic_id" }, "description": "Auto-generated description", "bindings": { @@ -319,14 +319,14 @@ }, "messages": [ { - "$ref": "#/channels/example-topic/messages/io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-topic_id/messages/io.github.springwolf.examples.cloudstream.dtos.ExamplePayloadDto" } ] }, - "google-pubsub-topic_publish_googlePubSubConsumerMethod": { + "google-pubsub-topic_id_publish_googlePubSubConsumerMethod": { "action": "receive", "channel": { - "$ref": "#/channels/google-pubsub-topic" + "$ref": "#/channels/google-pubsub-topic_id" }, "description": "Auto-generated description", "bindings": { @@ -334,7 +334,7 @@ }, "messages": [ { - "$ref": "#/channels/google-pubsub-topic/messages/io.github.springwolf.examples.cloudstream.dtos.GooglePubSubPayloadDto" + "$ref": "#/channels/google-pubsub-topic_id/messages/io.github.springwolf.examples.cloudstream.dtos.GooglePubSubPayloadDto" } ] } diff --git a/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json index c79361e1f..05a073ecb 100644 --- a/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "another-queue": { + "another-queue_id": { "address": "another-queue", "messages": { "io.github.springwolf.examples.jms.dtos.AnotherPayloadDto": { @@ -30,7 +30,7 @@ } } }, - "example-queue": { + "example-queue_id": { "address": "example-queue", "messages": { "io.github.springwolf.examples.jms.dtos.ExamplePayloadDto": { @@ -174,26 +174,26 @@ } }, "operations": { - "another-queue_receive_receiveAnotherPayload": { + "another-queue_id_receive_receiveAnotherPayload": { "action": "receive", "channel": { - "$ref": "#/channels/another-queue" + "$ref": "#/channels/another-queue_id" }, "bindings": { "jms": { } }, "messages": [ { - "$ref": "#/channels/another-queue/messages/io.github.springwolf.examples.jms.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-queue_id/messages/io.github.springwolf.examples.jms.dtos.AnotherPayloadDto" } ] }, - "another-queue_send_sendMessage": { + "another-queue_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/another-queue" + "$ref": "#/channels/another-queue_id" }, - "title": "another-queue_send", + "title": "another-queue_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "jms": { @@ -205,21 +205,21 @@ }, "messages": [ { - "$ref": "#/channels/another-queue/messages/io.github.springwolf.examples.jms.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-queue_id/messages/io.github.springwolf.examples.jms.dtos.AnotherPayloadDto" } ] }, - "example-queue_receive_receiveExamplePayload": { + "example-queue_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/example-queue" + "$ref": "#/channels/example-queue_id" }, "bindings": { "jms": { } }, "messages": [ { - "$ref": "#/channels/example-queue/messages/io.github.springwolf.examples.jms.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-queue_id/messages/io.github.springwolf.examples.jms.dtos.ExamplePayloadDto" } ] } diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json index 36591c1a3..f293c5313 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "another-topic": { + "another-topic_id": { "address": "another-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto": { @@ -35,7 +35,7 @@ } } }, - "avro-topic": { + "avro-topic_id": { "address": "avro-topic", "messages": { "io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto": { @@ -43,7 +43,7 @@ } } }, - "example-topic": { + "example-topic_id": { "address": "example-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto": { @@ -56,7 +56,7 @@ } } }, - "integer-topic": { + "integer-topic_id": { "address": "integer-topic", "messages": { "java.lang.Number": { @@ -69,7 +69,7 @@ } } }, - "multi-payload-topic": { + "multi-payload-topic_id": { "address": "multi-payload-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto": { @@ -83,7 +83,7 @@ } } }, - "no-payload-used-topic": { + "no-payload-used-topic_id": { "address": "no-payload-used-topic", "messages": { "PayloadNotUsed": { @@ -96,7 +96,7 @@ } } }, - "protobuf-topic": { + "protobuf-topic_id": { "address": "protobuf-topic", "messages": { "io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message": { @@ -109,7 +109,7 @@ } } }, - "string-topic": { + "string-topic_id": { "address": "string-topic", "messages": { "io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope": { @@ -120,7 +120,7 @@ } } }, - "topic-defined-via-asyncPublisher-annotation": { + "topic-defined-via-asyncPublisher-annotation_id": { "address": "topic-defined-via-asyncPublisher-annotation", "messages": { "io.github.springwolf.examples.kafka.dtos.NestedPayloadDto": { @@ -133,7 +133,7 @@ } ] }, - "vehicle-topic": { + "vehicle-topic_id": { "address": "vehicle-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase": { @@ -146,7 +146,7 @@ } } }, - "xml-topic": { + "xml-topic_id": { "address": "xml-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.XmlPayloadDto": { @@ -154,7 +154,7 @@ } } }, - "yaml-topic": { + "yaml-topic_id": { "address": "yaml-topic", "messages": { "io.github.springwolf.examples.kafka.dtos.YamlPayloadDto": { @@ -1744,10 +1744,10 @@ } }, "operations": { - "another-topic_receive_receiveAnotherPayloadBatched": { + "another-topic_id_receive_receiveAnotherPayloadBatched": { "action": "receive", "channel": { - "$ref": "#/channels/another-topic" + "$ref": "#/channels/another-topic_id" }, "bindings": { "kafka": { @@ -1762,16 +1762,16 @@ }, "messages": [ { - "$ref": "#/channels/another-topic/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-topic_id/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" } ] }, - "avro-topic_receive_receiveExampleAvroPayload": { + "avro-topic_id_receive_receiveExampleAvroPayload": { "action": "receive", "channel": { - "$ref": "#/channels/avro-topic" + "$ref": "#/channels/avro-topic_id" }, - "title": "avro-topic_receive", + "title": "avro-topic_id_receive", "description": "Requires a running kafka-schema-registry. See docker-compose.yml to start it", "bindings": { "kafka": { @@ -1780,14 +1780,14 @@ }, "messages": [ { - "$ref": "#/channels/avro-topic/messages/io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto" + "$ref": "#/channels/avro-topic_id/messages/io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto" } ] }, - "example-topic_receive_receiveExamplePayload": { + "example-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/example-topic" + "$ref": "#/channels/example-topic_id" }, "bindings": { "kafka": { @@ -1796,14 +1796,14 @@ }, "messages": [ { - "$ref": "#/channels/example-topic/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-topic_id/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" } ] }, - "integer-topic_receive_receiveIntegerPayload": { + "integer-topic_id_receive_receiveIntegerPayload": { "action": "receive", "channel": { - "$ref": "#/channels/integer-topic" + "$ref": "#/channels/integer-topic_id" }, "bindings": { "kafka": { @@ -1812,14 +1812,14 @@ }, "messages": [ { - "$ref": "#/channels/integer-topic/messages/java.lang.Number" + "$ref": "#/channels/integer-topic_id/messages/java.lang.Number" } ] }, - "multi-payload-topic_receive_ExampleClassLevelKafkaListener": { + "multi-payload-topic_id_receive_ExampleClassLevelKafkaListener": { "action": "receive", "channel": { - "$ref": "#/channels/multi-payload-topic" + "$ref": "#/channels/multi-payload-topic_id" }, "bindings": { "kafka": { @@ -1828,22 +1828,22 @@ }, "messages": [ { - "$ref": "#/channels/multi-payload-topic/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" + "$ref": "#/channels/multi-payload-topic_id/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" }, { - "$ref": "#/channels/multi-payload-topic/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" + "$ref": "#/channels/multi-payload-topic_id/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" }, { - "$ref": "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" + "$ref": "#/channels/multi-payload-topic_id/messages/javax.money.MonetaryAmount" } ] }, - "multi-payload-topic_receive_receiveMonetaryAmount": { + "multi-payload-topic_id_receive_receiveMonetaryAmount": { "action": "receive", "channel": { - "$ref": "#/channels/multi-payload-topic" + "$ref": "#/channels/multi-payload-topic_id" }, - "title": "multi-payload-topic_receive", + "title": "multi-payload-topic_id_receive", "description": "Override description in the AsyncListener annotation with servers at kafka:29092", "bindings": { "kafka": { @@ -1864,14 +1864,14 @@ }, "messages": [ { - "$ref": "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" + "$ref": "#/channels/multi-payload-topic_id/messages/javax.money.MonetaryAmount" } ] }, - "no-payload-used-topic_receive_receiveExamplePayload": { + "no-payload-used-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/no-payload-used-topic" + "$ref": "#/channels/no-payload-used-topic_id" }, "bindings": { "kafka": { @@ -1880,14 +1880,14 @@ }, "messages": [ { - "$ref": "#/channels/no-payload-used-topic/messages/PayloadNotUsed" + "$ref": "#/channels/no-payload-used-topic_id/messages/PayloadNotUsed" } ] }, - "protobuf-topic_receive_receiveExampleProtobufPayload": { + "protobuf-topic_id_receive_receiveExampleProtobufPayload": { "action": "receive", "channel": { - "$ref": "#/channels/protobuf-topic" + "$ref": "#/channels/protobuf-topic_id" }, "bindings": { "kafka": { @@ -1896,16 +1896,16 @@ }, "messages": [ { - "$ref": "#/channels/protobuf-topic/messages/io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message" + "$ref": "#/channels/protobuf-topic_id/messages/io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message" } ] }, - "string-topic_receive_receiveStringPayload": { + "string-topic_id_receive_receiveStringPayload": { "action": "receive", "channel": { - "$ref": "#/channels/string-topic" + "$ref": "#/channels/string-topic_id" }, - "title": "string-topic_receive", + "title": "string-topic_id_receive", "description": "Final classes (like String) can be documented using an envelope class and the @AsyncApiPayload annotation.", "bindings": { "kafka": { @@ -1914,19 +1914,19 @@ }, "messages": [ { - "$ref": "#/channels/string-topic/messages/io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope" + "$ref": "#/channels/string-topic_id/messages/io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope" }, { - "$ref": "#/channels/string-topic/messages/java.lang.String" + "$ref": "#/channels/string-topic_id/messages/java.lang.String" } ] }, - "topic-defined-via-asyncPublisher-annotation_send_sendMessage": { + "topic-defined-via-asyncPublisher-annotation_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation" + "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation_id" }, - "title": "topic-defined-via-asyncPublisher-annotation_send", + "title": "topic-defined-via-asyncPublisher-annotation_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "kafka": { @@ -1941,14 +1941,14 @@ }, "messages": [ { - "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation/messages/io.github.springwolf.examples.kafka.dtos.NestedPayloadDto" + "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation_id/messages/io.github.springwolf.examples.kafka.dtos.NestedPayloadDto" } ] }, - "vehicle-topic_receive_receiveExamplePayload": { + "vehicle-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/vehicle-topic" + "$ref": "#/channels/vehicle-topic_id" }, "bindings": { "kafka": { @@ -1957,16 +1957,16 @@ }, "messages": [ { - "$ref": "#/channels/vehicle-topic/messages/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" + "$ref": "#/channels/vehicle-topic_id/messages/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" } ] }, - "xml-topic_receive_receiveExamplePayload": { + "xml-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/xml-topic" + "$ref": "#/channels/xml-topic_id" }, - "title": "xml-topic_receive", + "title": "xml-topic_id_receive", "description": "Auto-generated description", "bindings": { "kafka": { @@ -1975,16 +1975,16 @@ }, "messages": [ { - "$ref": "#/channels/xml-topic/messages/io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + "$ref": "#/channels/xml-topic_id/messages/io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" } ] }, - "yaml-topic_receive_receiveExamplePayload": { + "yaml-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/yaml-topic" + "$ref": "#/channels/yaml-topic_id" }, - "title": "yaml-topic_receive", + "title": "yaml-topic_id_receive", "description": "Auto-generated description", "bindings": { "kafka": { @@ -1993,9 +1993,9 @@ }, "messages": [ { - "$ref": "#/channels/yaml-topic/messages/io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + "$ref": "#/channels/yaml-topic_id/messages/io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" } ] } } -} +} \ No newline at end of file diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml index a30c80ede..5ac32451e 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.yaml @@ -17,7 +17,7 @@ servers: host: kafka:29092 protocol: kafka channels: - another-topic: + another-topic_id: address: another-topic messages: io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto: @@ -25,12 +25,12 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - avro-topic: + avro-topic_id: address: avro-topic messages: io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto: $ref: "#/components/messages/io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto" - example-topic: + example-topic_id: address: example-topic messages: io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto: @@ -38,7 +38,7 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - integer-topic: + integer-topic_id: address: integer-topic messages: java.lang.Number: @@ -46,7 +46,7 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - multi-payload-topic: + multi-payload-topic_id: address: multi-payload-topic messages: io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto: @@ -55,7 +55,7 @@ channels: $ref: "#/components/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" javax.money.MonetaryAmount: $ref: "#/components/messages/javax.money.MonetaryAmount" - no-payload-used-topic: + no-payload-used-topic_id: address: no-payload-used-topic messages: PayloadNotUsed: @@ -63,7 +63,7 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - protobuf-topic: + protobuf-topic_id: address: protobuf-topic messages: io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message: @@ -71,21 +71,21 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - string-topic: + string-topic_id: address: string-topic messages: io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope: $ref: "#/components/messages/io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope" java.lang.String: $ref: "#/components/messages/java.lang.String" - topic-defined-via-asyncPublisher-annotation: + topic-defined-via-asyncPublisher-annotation_id: address: topic-defined-via-asyncPublisher-annotation messages: io.github.springwolf.examples.kafka.dtos.NestedPayloadDto: $ref: "#/components/messages/io.github.springwolf.examples.kafka.dtos.NestedPayloadDto" servers: - $ref: "#/servers/kafka-server" - vehicle-topic: + vehicle-topic_id: address: vehicle-topic messages: io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase: @@ -93,12 +93,12 @@ channels: bindings: kafka: bindingVersion: 0.5.0 - xml-topic: + xml-topic_id: address: xml-topic messages: io.github.springwolf.examples.kafka.dtos.XmlPayloadDto: $ref: "#/components/messages/io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" - yaml-topic: + yaml-topic_id: address: yaml-topic messages: io.github.springwolf.examples.kafka.dtos.YamlPayloadDto: @@ -1257,10 +1257,10 @@ components: - example-key bindingVersion: 0.5.0 operations: - another-topic_receive_receiveAnotherPayloadBatched: + another-topic_id_receive_receiveAnotherPayloadBatched: action: receive channel: - $ref: "#/channels/another-topic" + $ref: "#/channels/another-topic_id" bindings: kafka: groupId: @@ -1269,53 +1269,53 @@ operations: - example-group-id bindingVersion: 0.5.0 messages: - - $ref: "#/channels/another-topic/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" - avro-topic_receive_receiveExampleAvroPayload: + - $ref: "#/channels/another-topic_id/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" + avro-topic_id_receive_receiveExampleAvroPayload: action: receive channel: - $ref: "#/channels/avro-topic" - title: avro-topic_receive + $ref: "#/channels/avro-topic_id" + title: avro-topic_id_receive description: Requires a running kafka-schema-registry. See docker-compose.yml to start it bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/avro-topic/messages/io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto" - example-topic_receive_receiveExamplePayload: + - $ref: "#/channels/avro-topic_id/messages/io.github.springwolf.examples.kafka.dto.avro.AnotherPayloadAvroDto" + example-topic_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/example-topic" + $ref: "#/channels/example-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/example-topic/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" - integer-topic_receive_receiveIntegerPayload: + - $ref: "#/channels/example-topic_id/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" + integer-topic_id_receive_receiveIntegerPayload: action: receive channel: - $ref: "#/channels/integer-topic" + $ref: "#/channels/integer-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/integer-topic/messages/java.lang.Number" - multi-payload-topic_receive_ExampleClassLevelKafkaListener: + - $ref: "#/channels/integer-topic_id/messages/java.lang.Number" + multi-payload-topic_id_receive_ExampleClassLevelKafkaListener: action: receive channel: - $ref: "#/channels/multi-payload-topic" + $ref: "#/channels/multi-payload-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/multi-payload-topic/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" - - $ref: "#/channels/multi-payload-topic/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" - - $ref: "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" - multi-payload-topic_receive_receiveMonetaryAmount: + - $ref: "#/channels/multi-payload-topic_id/messages/io.github.springwolf.examples.kafka.dtos.ExamplePayloadDto" + - $ref: "#/channels/multi-payload-topic_id/messages/io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto" + - $ref: "#/channels/multi-payload-topic_id/messages/javax.money.MonetaryAmount" + multi-payload-topic_id_receive_receiveMonetaryAmount: action: receive channel: - $ref: "#/channels/multi-payload-topic" - title: multi-payload-topic_receive + $ref: "#/channels/multi-payload-topic_id" + title: multi-payload-topic_id_receive description: Override description in the AsyncListener annotation with servers at kafka:29092 bindings: @@ -1330,43 +1330,43 @@ operations: - foo-clientId bindingVersion: 0.5.0 messages: - - $ref: "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" - no-payload-used-topic_receive_receiveExamplePayload: + - $ref: "#/channels/multi-payload-topic_id/messages/javax.money.MonetaryAmount" + no-payload-used-topic_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/no-payload-used-topic" + $ref: "#/channels/no-payload-used-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/no-payload-used-topic/messages/PayloadNotUsed" - protobuf-topic_receive_receiveExampleProtobufPayload: + - $ref: "#/channels/no-payload-used-topic_id/messages/PayloadNotUsed" + protobuf-topic_id_receive_receiveExampleProtobufPayload: action: receive channel: - $ref: "#/channels/protobuf-topic" + $ref: "#/channels/protobuf-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/protobuf-topic/messages/io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message" - string-topic_receive_receiveStringPayload: + - $ref: "#/channels/protobuf-topic_id/messages/io.github.springwolf.examples.kafka.dto.proto.ExamplePayloadProtobufDto$Message" + string-topic_id_receive_receiveStringPayload: action: receive channel: - $ref: "#/channels/string-topic" - title: string-topic_receive + $ref: "#/channels/string-topic_id" + title: string-topic_id_receive description: Final classes (like String) can be documented using an envelope class and the @AsyncApiPayload annotation. bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/string-topic/messages/io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope" - - $ref: "#/channels/string-topic/messages/java.lang.String" - topic-defined-via-asyncPublisher-annotation_send_sendMessage: + - $ref: "#/channels/string-topic_id/messages/io.github.springwolf.examples.kafka.consumers.StringConsumer$StringEnvelope" + - $ref: "#/channels/string-topic_id/messages/java.lang.String" + topic-defined-via-asyncPublisher-annotation_id_send_sendMessage: action: send channel: - $ref: "#/channels/topic-defined-via-asyncPublisher-annotation" - title: topic-defined-via-asyncPublisher-annotation_send + $ref: "#/channels/topic-defined-via-asyncPublisher-annotation_id" + title: topic-defined-via-asyncPublisher-annotation_id_send description: "Custom, optional description defined in the AsyncPublisher annotation" bindings: kafka: @@ -1376,35 +1376,35 @@ operations: - foo-clientId bindingVersion: 0.5.0 messages: - - $ref: "#/channels/topic-defined-via-asyncPublisher-annotation/messages/io.github.springwolf.examples.kafka.dtos.NestedPayloadDto" - vehicle-topic_receive_receiveExamplePayload: + - $ref: "#/channels/topic-defined-via-asyncPublisher-annotation_id/messages/io.github.springwolf.examples.kafka.dtos.NestedPayloadDto" + vehicle-topic_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/vehicle-topic" + $ref: "#/channels/vehicle-topic_id" bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/vehicle-topic/messages/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" - xml-topic_receive_receiveExamplePayload: + - $ref: "#/channels/vehicle-topic_id/messages/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" + xml-topic_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/xml-topic" - title: xml-topic_receive + $ref: "#/channels/xml-topic_id" + title: xml-topic_id_receive description: Auto-generated description bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/xml-topic/messages/io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" - yaml-topic_receive_receiveExamplePayload: + - $ref: "#/channels/xml-topic_id/messages/io.github.springwolf.examples.kafka.dtos.XmlPayloadDto" + yaml-topic_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/yaml-topic" - title: yaml-topic_receive + $ref: "#/channels/yaml-topic_id" + title: yaml-topic_id_receive description: Auto-generated description bindings: kafka: bindingVersion: 0.5.0 messages: - - $ref: "#/channels/yaml-topic/messages/io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" + - $ref: "#/channels/yaml-topic_id/messages/io.github.springwolf.examples.kafka.dtos.YamlPayloadDto" diff --git a/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json index d98eeed83..9b6974a9a 100644 --- a/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "another-topic": { + "another-topic_id": { "address": "another-topic", "messages": { "io.github.springwolf.examples.sns.dtos.AnotherPayloadDto": { @@ -30,7 +30,7 @@ } } }, - "example-topic": { + "example-topic_id": { "address": "example-topic", "messages": { "io.github.springwolf.examples.sns.dtos.ExamplePayloadDto": { @@ -249,12 +249,12 @@ } }, "operations": { - "another-topic_receive_receiveAnotherPayload": { + "another-topic_id_receive_receiveAnotherPayload": { "action": "receive", "channel": { - "$ref": "#/channels/another-topic" + "$ref": "#/channels/another-topic_id" }, - "title": "another-topic_receive", + "title": "another-topic_id_receive", "description": "Auto-generated description", "bindings": { "sns": { @@ -271,16 +271,16 @@ }, "messages": [ { - "$ref": "#/channels/another-topic/messages/io.github.springwolf.examples.sns.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-topic_id/messages/io.github.springwolf.examples.sns.dtos.AnotherPayloadDto" } ] }, - "another-topic_send_sendMessage": { + "another-topic_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/another-topic" + "$ref": "#/channels/another-topic_id" }, - "title": "another-topic_send", + "title": "another-topic_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "sns": { @@ -297,16 +297,16 @@ }, "messages": [ { - "$ref": "#/channels/another-topic/messages/io.github.springwolf.examples.sns.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-topic_id/messages/io.github.springwolf.examples.sns.dtos.AnotherPayloadDto" } ] }, - "example-topic_receive_receiveExamplePayload": { + "example-topic_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/example-topic" + "$ref": "#/channels/example-topic_id" }, - "title": "example-topic_receive", + "title": "example-topic_id_receive", "description": "Auto-generated description", "bindings": { "sns": { @@ -323,7 +323,7 @@ }, "messages": [ { - "$ref": "#/channels/example-topic/messages/io.github.springwolf.examples.sns.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-topic_id/messages/io.github.springwolf.examples.sns.dtos.ExamplePayloadDto" } ] } diff --git a/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json index 07d3652d1..957345bd8 100644 --- a/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "another-queue": { + "another-queue_id": { "address": "another-queue", "messages": { "io.github.springwolf.examples.sqs.dtos.AnotherPayloadDto": { @@ -30,7 +30,7 @@ } } }, - "example-queue": { + "example-queue_id": { "address": "example-queue", "messages": { "io.github.springwolf.examples.sqs.dtos.ExamplePayloadDto": { @@ -178,10 +178,10 @@ } }, "operations": { - "another-queue_receive_receiveAnotherPayload": { + "another-queue_id_receive_receiveAnotherPayload": { "action": "receive", "channel": { - "$ref": "#/channels/another-queue" + "$ref": "#/channels/another-queue_id" }, "bindings": { "sqs": { @@ -200,16 +200,16 @@ }, "messages": [ { - "$ref": "#/channels/another-queue/messages/io.github.springwolf.examples.sqs.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-queue_id/messages/io.github.springwolf.examples.sqs.dtos.AnotherPayloadDto" } ] }, - "another-queue_send_sendMessage": { + "another-queue_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/another-queue" + "$ref": "#/channels/another-queue_id" }, - "title": "another-queue_send", + "title": "another-queue_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "sqs": { @@ -228,14 +228,14 @@ }, "messages": [ { - "$ref": "#/channels/another-queue/messages/io.github.springwolf.examples.sqs.dtos.AnotherPayloadDto" + "$ref": "#/channels/another-queue_id/messages/io.github.springwolf.examples.sqs.dtos.AnotherPayloadDto" } ] }, - "example-queue_receive_receiveExamplePayload": { + "example-queue_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/example-queue" + "$ref": "#/channels/example-queue_id" }, "bindings": { "sqs": { @@ -254,7 +254,7 @@ }, "messages": [ { - "$ref": "#/channels/example-queue/messages/io.github.springwolf.examples.sqs.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-queue_id/messages/io.github.springwolf.examples.sqs.dtos.ExamplePayloadDto" } ] } diff --git a/springwolf-examples/springwolf-stomp-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-stomp-example/src/test/resources/asyncapi.json index dfe1fc9d0..f2a9e5451 100644 --- a/springwolf-examples/springwolf-stomp-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-stomp-example/src/test/resources/asyncapi.json @@ -22,7 +22,7 @@ } }, "channels": { - "_app_queue_another-queue": { + "_app_queue_another-queue_id": { "address": "/app/queue/another-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.AnotherPayloadDto": { @@ -30,7 +30,7 @@ } } }, - "_app_queue_example-queue": { + "_app_queue_example-queue_id": { "address": "/app/queue/example-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto": { @@ -41,7 +41,7 @@ "stomp": { } } }, - "_app_queue_sendto-queue": { + "_app_queue_sendto-queue_id": { "address": "/app/queue/sendto-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto": { @@ -52,7 +52,7 @@ "stomp": { } } }, - "_app_queue_sendtouser-queue": { + "_app_queue_sendtouser-queue_id": { "address": "/app/queue/sendtouser-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto": { @@ -63,7 +63,7 @@ "stomp": { } } }, - "_app_topic_sendto-response-queue": { + "_app_topic_sendto-response-queue_id": { "address": "/app/topic/sendto-response-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto": { @@ -74,7 +74,7 @@ "stomp": { } } }, - "_user_queue_sendtouser-response-queue": { + "_user_queue_sendtouser-response-queue_id": { "address": "/user/queue/sendtouser-response-queue", "messages": { "io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto": { @@ -220,80 +220,80 @@ } }, "operations": { - "_app_queue_another-queue_send_sendMessage": { + "_app_queue_another-queue_id_send_sendMessage": { "action": "send", "channel": { - "$ref": "#/channels/_app_queue_another-queue" + "$ref": "#/channels/_app_queue_another-queue_id" }, - "title": "_app_queue_another-queue_send", + "title": "_app_queue_another-queue_id_send", "description": "Custom, optional description defined in the AsyncPublisher annotation", "bindings": { "stomp": { } }, "messages": [ { - "$ref": "#/channels/_app_queue_another-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.AnotherPayloadDto" + "$ref": "#/channels/_app_queue_another-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.AnotherPayloadDto" } ] }, - "_app_queue_example-queue_receive_receiveExamplePayload": { + "_app_queue_example-queue_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/_app_queue_example-queue" + "$ref": "#/channels/_app_queue_example-queue_id" }, "bindings": { "stomp": { } }, "messages": [ { - "$ref": "#/channels/_app_queue_example-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" + "$ref": "#/channels/_app_queue_example-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" } ] }, - "_app_queue_sendto-queue_receive_receiveExamplePayloadSendTo": { + "_app_queue_sendto-queue_id_receive_receiveExamplePayloadSendTo": { "action": "receive", "channel": { - "$ref": "#/channels/_app_queue_sendto-queue" + "$ref": "#/channels/_app_queue_sendto-queue_id" }, "bindings": { "stomp": { } }, "messages": [ { - "$ref": "#/channels/_app_queue_sendto-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" + "$ref": "#/channels/_app_queue_sendto-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" } ], "reply": { "channel": { - "$ref": "#/channels/_app_topic_sendto-response-queue" + "$ref": "#/channels/_app_topic_sendto-response-queue_id" }, "messages": [ { - "$ref": "#/channels/_app_topic_sendto-response-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" + "$ref": "#/channels/_app_topic_sendto-response-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" } ] } }, - "_app_queue_sendtouser-queue_receive_receiveExamplePayloadSendToUser": { + "_app_queue_sendtouser-queue_id_receive_receiveExamplePayloadSendToUser": { "action": "receive", "channel": { - "$ref": "#/channels/_app_queue_sendtouser-queue" + "$ref": "#/channels/_app_queue_sendtouser-queue_id" }, "bindings": { "stomp": { } }, "messages": [ { - "$ref": "#/channels/_app_queue_sendtouser-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" + "$ref": "#/channels/_app_queue_sendtouser-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" } ], "reply": { "channel": { - "$ref": "#/channels/_user_queue_sendtouser-response-queue" + "$ref": "#/channels/_user_queue_sendtouser-response-queue_id" }, "messages": [ { - "$ref": "#/channels/_user_queue_sendtouser-response-queue/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" + "$ref": "#/channels/_user_queue_sendtouser-response-queue_id/messages/io.github.springwolf.examples.stomp.stomp.dtos.ExamplePayloadDto" } ] } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 6c8588860..541f3ff78 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -31,8 +31,6 @@ import java.util.Optional; import java.util.stream.Stream; -import static io.github.springwolf.asyncapi.v3.model.ReferenceUtil.ID_POSTFIX; - /** * Note: bindings, queues, and queuesToDeclare are mutually exclusive *
      @@ -76,7 +74,7 @@ public static String getChannelId(RabbitListener annotation, StringValueResolver Stream annotationBindingChannelIds = Arrays.stream(annotation.bindings()) .flatMap(binding -> channelIdFromAnnotationBindings(binding, resolver)); - return Stream.concat(streamQueueNames(annotation).map(name -> name + ID_POSTFIX), annotationBindingChannelIds) + return Stream.concat(streamQueueNames(annotation).map(ReferenceUtil::toValidId), annotationBindingChannelIds) .map(resolver::resolveStringValue) .filter(Objects::nonNull) .peek(queue -> log.debug("Resolved channel id: {}", queue)) @@ -236,7 +234,7 @@ private static AMQPChannelQueueProperties buildQueueProperties( public static ChannelObject buildChannelObject(org.springframework.amqp.core.Queue queue) { return ChannelObject.builder() - .channelId(ReferenceUtil.toValidId(queue.getName()) + ID_POSTFIX) + .channelId(ReferenceUtil.toValidId(queue.getName())) .address(queue.getName()) .bindings(Map.of( BINDING_NAME, diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java index 25d4abe08..f990ec448 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/springwolf/plugins/cloudstream/asyncapi/scanners/channels/CloudStreamFunctionChannelsScannerIntegrationTest.java @@ -56,6 +56,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import static io.github.springwolf.asyncapi.v3.model.ReferenceUtil.ID_POSTFIX; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -141,7 +142,7 @@ void testConsumerBinding() { .build(); ChannelObject expectedChannel = ChannelObject.builder() - .channelId(topicName) + .channelId(topicName + ID_POSTFIX) .address(topicName) .bindings(channelBinding) .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) @@ -151,13 +152,13 @@ void testConsumerBinding() { .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(topicName)) - .messages(List.of(MessageReference.toChannelMessage(topicName, message))) + .channel(ChannelReference.fromChannel(topicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(topicName + ID_POSTFIX, message))) .build(); - assertThat(actualChannels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualChannels).containsExactly(Map.entry(topicName + ID_POSTFIX, expectedChannel)); assertThat(actualOperations) - .containsExactly(Map.entry("test-consumer-input-topic_publish_testConsumer", expectedOperation)); + .containsExactly(Map.entry("test-consumer-input-topic_id_publish_testConsumer", expectedOperation)); assertThat(componentsService.getMessages()).contains(Map.entry(String.class.getName(), message)); } @@ -191,20 +192,20 @@ void testSupplierBinding() { .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(topicName)) - .messages(List.of(MessageReference.toChannelMessage(topicName, message))) + .channel(ChannelReference.fromChannel(topicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(topicName + ID_POSTFIX, message))) .build(); ChannelObject expectedChannel = ChannelObject.builder() - .channelId(topicName) + .channelId(topicName + ID_POSTFIX) .address(topicName) .bindings(channelBinding) .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); - assertThat(actualChannels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualChannels).containsExactly(Map.entry(topicName + ID_POSTFIX, expectedChannel)); assertThat(actualOperations) - .containsExactly(Map.entry("test-supplier-output-topic_subscribe_testSupplier", expectedOperation)); + .containsExactly(Map.entry("test-supplier-output-topic_id_subscribe_testSupplier", expectedOperation)); assertThat(componentsService.getMessages()).contains(Map.entry(String.class.getName(), message)); } @@ -245,12 +246,12 @@ void testFunctionBinding() { .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(outputTopicName)) - .messages(List.of(MessageReference.toChannelMessage(outputTopicName, subscribeMessage))) + .channel(ChannelReference.fromChannel(outputTopicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(outputTopicName + ID_POSTFIX, subscribeMessage))) .build(); ChannelObject subscribeChannel = ChannelObject.builder() - .channelId(outputTopicName) + .channelId(outputTopicName + ID_POSTFIX) .address(outputTopicName) .bindings(channelBinding) .messages( @@ -273,23 +274,25 @@ void testFunctionBinding() { .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(inputTopicName)) - .messages(List.of(MessageReference.toChannelMessage(inputTopicName, publishMessage))) + .channel(ChannelReference.fromChannel(inputTopicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(inputTopicName + ID_POSTFIX, publishMessage))) .build(); ChannelObject publishChannel = ChannelObject.builder() - .channelId(inputTopicName) + .channelId(inputTopicName + ID_POSTFIX) .address(inputTopicName) .bindings(channelBinding) .messages(Map.of(publishMessage.getMessageId(), MessageReference.toComponentMessage(publishMessage))) .build(); assertThat(actualChannels) - .contains(Map.entry(inputTopicName, publishChannel), Map.entry(outputTopicName, subscribeChannel)); + .contains( + Map.entry(inputTopicName + ID_POSTFIX, publishChannel), + Map.entry(outputTopicName + ID_POSTFIX, subscribeChannel)); assertThat(actualOperations) .contains( - Map.entry("test-in-topic_publish_testFunction", publishOperation), - Map.entry("test-out-topic_subscribe_testFunction", subscribeOperation)); + Map.entry("test-in-topic_id_publish_testFunction", publishOperation), + Map.entry("test-out-topic_id_subscribe_testFunction", subscribeOperation)); } @Test @@ -329,12 +332,12 @@ void testKStreamFunctionBinding() { .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(outputTopicName)) - .messages(List.of(MessageReference.toChannelMessage(outputTopicName, subscribeMessage))) + .channel(ChannelReference.fromChannel(outputTopicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(outputTopicName + ID_POSTFIX, subscribeMessage))) .build(); ChannelObject subscribeChannel = ChannelObject.builder() - .channelId(outputTopicName) + .channelId(outputTopicName + ID_POSTFIX) .address(outputTopicName) .bindings(channelBinding) .messages( @@ -357,23 +360,25 @@ void testKStreamFunctionBinding() { .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(inputTopicName)) - .messages(List.of(MessageReference.toChannelMessage(inputTopicName, publishMessage))) + .channel(ChannelReference.fromChannel(inputTopicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(inputTopicName + ID_POSTFIX, publishMessage))) .build(); ChannelObject publishChannel = ChannelObject.builder() - .channelId(inputTopicName) + .channelId(inputTopicName + ID_POSTFIX) .address(inputTopicName) .bindings(channelBinding) .messages(Map.of(publishMessage.getMessageId(), MessageReference.toComponentMessage(publishMessage))) .build(); assertThat(actualChannels) - .contains(Map.entry(inputTopicName, publishChannel), Map.entry(outputTopicName, subscribeChannel)); + .contains( + Map.entry(inputTopicName + ID_POSTFIX, publishChannel), + Map.entry(outputTopicName + ID_POSTFIX, subscribeChannel)); assertThat(actualOperations) .contains( - Map.entry("test-in-topic_publish_kStreamTestFunction", publishOperation), - Map.entry("test-out-topic_subscribe_kStreamTestFunction", subscribeOperation)); + Map.entry("test-in-topic_id_publish_kStreamTestFunction", publishOperation), + Map.entry("test-out-topic_id_subscribe_kStreamTestFunction", subscribeOperation)); assertThat(componentsService.getMessages()).contains(Map.entry(String.class.getName(), publishMessage)); assertThat(componentsService.getMessages()).contains(Map.entry(Integer.class.getName(), subscribeMessage)); } @@ -414,8 +419,8 @@ void testFunctionBindingWithSameTopicName() { .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(topicName)) - .messages(List.of(MessageReference.toChannelMessage(topicName, subscribeMessage))) + .channel(ChannelReference.fromChannel(topicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(topicName + ID_POSTFIX, subscribeMessage))) .build(); MessageObject publishMessage = MessageObject.builder() @@ -435,12 +440,12 @@ void testFunctionBindingWithSameTopicName() { .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .channel(ChannelReference.fromChannel(topicName)) - .messages(List.of(MessageReference.toChannelMessage(topicName, publishMessage))) + .channel(ChannelReference.fromChannel(topicName + ID_POSTFIX)) + .messages(List.of(MessageReference.toChannelMessage(topicName + ID_POSTFIX, publishMessage))) .build(); ChannelObject mergedChannel = ChannelObject.builder() - .channelId(topicName) + .channelId(topicName + ID_POSTFIX) .address(topicName) .bindings(channelBinding) .messages(Map.of( @@ -450,11 +455,11 @@ void testFunctionBindingWithSameTopicName() { MessageReference.toComponentMessage(subscribeMessage))) .build(); - assertThat(actualChannels).contains(Map.entry(topicName, mergedChannel)); + assertThat(actualChannels).contains(Map.entry(topicName + ID_POSTFIX, mergedChannel)); assertThat(actualOperations) .contains( - Map.entry("test-topic_publish_testFunction", publishOperation), - Map.entry("test-topic_subscribe_testFunction", subscribeOperation)); + Map.entry("test-topic_id_publish_testFunction", publishOperation), + Map.entry("test-topic_id_subscribe_testFunction", subscribeOperation)); assertThat(componentsService.getMessages()).contains(Map.entry(String.class.getName(), publishMessage)); assertThat(componentsService.getMessages()).contains(Map.entry(Integer.class.getName(), subscribeMessage)); } From 65f750d5bfef298fd1caf37211cc05a0424ca16f Mon Sep 17 00:00:00 2001 From: Timon Back Date: Sun, 8 Sep 2024 22:44:57 +0200 Subject: [PATCH 08/10] test(amqp): update after rebase --- .../src/test/resources/asyncapi.json | 270 +++++++++--------- .../src/test/resources/asyncapi.yaml | 232 +++++++-------- 2 files changed, 257 insertions(+), 245 deletions(-) diff --git a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json index bb02346b5..206b879b8 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json @@ -25,32 +25,29 @@ } }, "channels": { - "#": { - "address": "#", + "another-queue_id": { + "address": "another-queue", "messages": { - "io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto": { - "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" - }, - "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { - "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + "io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" } }, "bindings": { "amqp": { - "is": "routingKey", - "exchange": { - "name": "CRUD-topic-exchange-1", - "type": "topic", - "durable": true, + "is": "queue", + "queue": { + "name": "another-queue", + "durable": false, + "exclusive": false, "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "another-queue": { - "address": "another-queue", + "example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id": { + "address": "example-topic-exchange", "messages": { "io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto": { "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" @@ -58,19 +55,23 @@ }, "bindings": { "amqp": { - "is": "queue", - "queue": { - "name": "another-queue", - "durable": false, - "exclusive": false, + "channel": { + "$ref": "#/channels/example-bindings-queue-id" + }, + "is": "routingKey", + "name": "example-topic-routing-key", + "exchange": { + "name": "example-topic-exchange", + "type": "topic", + "durable": true, "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "example-bindings-queue": { + "example-bindings-queue_id": { "address": "example-bindings-queue", "bindings": { "amqp": { @@ -78,15 +79,15 @@ "queue": { "name": "example-bindings-queue", "durable": false, - "exclusive": false, + "exclusive": true, "autoDelete": true, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "example-queue": { + "example-queue_id": { "address": "example-queue", "messages": { "io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto": { @@ -103,7 +104,7 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, @@ -115,28 +116,7 @@ } } }, - "example-topic-routing-key": { - "address": "example-topic-routing-key", - "messages": { - "io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto": { - "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - } - }, - "bindings": { - "amqp": { - "is": "routingKey", - "exchange": { - "name": "example-topic-exchange", - "type": "topic", - "durable": true, - "autoDelete": false, - "vhost": "/" - }, - "bindingVersion": "0.3.0" - } - } - }, - "multi-payload-queue": { + "multi-payload-queue_id": { "address": "multi-payload-queue", "messages": { "io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto": { @@ -156,11 +136,11 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "queue-create": { + "queue-create_id": { "address": "queue-create", "messages": { "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { @@ -177,11 +157,11 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "queue-delete": { + "queue-delete_id": { "address": "queue-delete", "messages": { "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { @@ -198,11 +178,36 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, - "queue-read": { + "queue-read-id_#_CRUD-topic-exchange-2-id": { + "address": "CRUD-topic-exchange-2", + "messages": { + "io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + } + }, + "bindings": { + "amqp": { + "channel": { + "$ref": "#/channels/queue-read-id" + }, + "is": "routingKey", + "name": "#", + "exchange": { + "name": "CRUD-topic-exchange-2", + "type": "topic", + "durable": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.4.0" + } + } + }, + "queue-read_id": { "address": "queue-read", "bindings": { "amqp": { @@ -214,11 +219,36 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" + } + } + }, + "queue-update-id_#_CRUD-topic-exchange-1-id": { + "address": "CRUD-topic-exchange-1", + "messages": { + "io.github.springwolf.examples.amqp.dtos.GenericPayloadDto": { + "$ref": "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + }, + "bindings": { + "amqp": { + "channel": { + "$ref": "#/channels/queue-update-id" + }, + "is": "routingKey", + "name": "#", + "exchange": { + "name": "CRUD-topic-exchange-1", + "type": "topic", + "durable": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.4.0" } } }, - "queue-update": { + "queue-update_id": { "address": "queue-update", "bindings": { "amqp": { @@ -230,7 +260,7 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } } @@ -367,7 +397,7 @@ "description": "Another payload model", "bindings": { "amqp": { - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, @@ -385,7 +415,7 @@ "title": "ExamplePayloadDto", "bindings": { "amqp": { - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } }, @@ -403,90 +433,61 @@ "title": "GenericPayloadDto", "bindings": { "amqp": { - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } } } } }, "operations": { - "#_receive_bindingsRead": { + "another-queue_id_receive_receiveAnotherPayload": { "action": "receive", "channel": { - "$ref": "#/channels/#" + "$ref": "#/channels/another-queue_id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "#" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + "$ref": "#/channels/another-queue_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" } ] }, - "#_receive_bindingsUpdate": { + "example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id_receive_bindingsExample": { "action": "receive", "channel": { - "$ref": "#/channels/#" + "$ref": "#/channels/example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "#" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + "$ref": "#/channels/example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" } ] }, - "another-queue_receive_receiveAnotherPayload": { + "example-queue_id_receive_receiveExamplePayload": { "action": "receive", "channel": { - "$ref": "#/channels/another-queue" + "$ref": "#/channels/example-queue_id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "another-queue" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/another-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - } - ] - }, - "example-queue_receive_receiveExamplePayload": { - "action": "receive", - "channel": { - "$ref": "#/channels/example-queue" - }, - "bindings": { - "amqp": { - "expiration": 0, - "cc": [ - "example-queue" - ], - "bindingVersion": "0.3.0" - } - }, - "messages": [ - { - "$ref": "#/channels/example-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + "$ref": "#/channels/example-queue_id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" } ] }, @@ -506,7 +507,7 @@ "mandatory": false, "timestamp": false, "ack": false, - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ @@ -515,86 +516,91 @@ } ] }, - "example-topic-routing-key_receive_bindingsExample": { + "multi-payload-queue_id_receive_bindingsBeanExample": { "action": "receive", "channel": { - "$ref": "#/channels/example-topic-routing-key" + "$ref": "#/channels/multi-payload-queue_id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "example-topic-routing-key" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/example-topic-routing-key/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + "$ref": "#/channels/multi-payload-queue_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + }, + { + "$ref": "#/channels/multi-payload-queue_id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" } ] }, - "multi-payload-queue_receive_bindingsBeanExample": { + "queue-create_id_receive_queuesToDeclareCreate": { "action": "receive", "channel": { - "$ref": "#/channels/multi-payload-queue" + "$ref": "#/channels/queue-create_id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "multi-payload-queue" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - }, + "$ref": "#/channels/queue-create_id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + } + ] + }, + "queue-delete_id_receive_queuesToDeclareDelete": { + "action": "receive", + "channel": { + "$ref": "#/channels/queue-delete_id" + }, + "bindings": { + "amqp": { + "expiration": 0, + "bindingVersion": "0.4.0" + } + }, + "messages": [ { - "$ref": "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + "$ref": "#/channels/queue-delete_id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" } ] }, - "queue-create_receive_queuesToDeclareCreate": { + "queue-read-id_#_CRUD-topic-exchange-2-id_receive_bindingsRead": { "action": "receive", "channel": { - "$ref": "#/channels/queue-create" + "$ref": "#/channels/queue-read-id_#_CRUD-topic-exchange-2-id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "queue-create" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/queue-create/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + "$ref": "#/channels/queue-read-id_#_CRUD-topic-exchange-2-id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" } ] }, - "queue-delete_receive_queuesToDeclareDelete": { + "queue-update-id_#_CRUD-topic-exchange-1-id_receive_bindingsUpdate": { "action": "receive", "channel": { - "$ref": "#/channels/queue-delete" + "$ref": "#/channels/queue-update-id_#_CRUD-topic-exchange-1-id" }, "bindings": { "amqp": { "expiration": 0, - "cc": [ - "queue-delete" - ], - "bindingVersion": "0.3.0" + "bindingVersion": "0.4.0" } }, "messages": [ { - "$ref": "#/channels/queue-delete/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + "$ref": "#/channels/queue-update-id_#_CRUD-topic-exchange-1-id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" } ] } diff --git a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml index ca18082d5..87127d0d4 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.yaml @@ -19,24 +19,7 @@ servers: host: amqp:5672 protocol: amqp channels: - '#': - address: "#" - messages: - io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: - $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" - io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: - $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" - bindings: - amqp: - is: routingKey - exchange: - name: CRUD-topic-exchange-1 - type: topic - durable: true - autoDelete: false - vhost: / - bindingVersion: 0.3.0 - another-queue: + another-queue_id: address: another-queue messages: io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: @@ -50,8 +33,26 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - example-bindings-queue: + bindingVersion: 0.4.0 + example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id: + address: example-topic-exchange + messages: + io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + bindings: + amqp: + channel: + $ref: "#/channels/example-bindings-queue-id" + is: routingKey + name: example-topic-routing-key + exchange: + name: example-topic-exchange + type: topic + durable: true + autoDelete: false + vhost: / + bindingVersion: 0.4.0 + example-bindings-queue_id: address: example-bindings-queue bindings: amqp: @@ -59,11 +60,11 @@ channels: queue: name: example-bindings-queue durable: false - exclusive: false + exclusive: true autoDelete: true vhost: / - bindingVersion: 0.3.0 - example-queue: + bindingVersion: 0.4.0 + example-queue_id: address: example-queue messages: io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: @@ -77,28 +78,13 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - example-topic-exchange: + bindingVersion: 0.4.0 + example-topic-exchange_id: address: example-topic-exchange messages: io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - example-topic-routing-key: - address: example-topic-routing-key - messages: - io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: - $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - bindings: - amqp: - is: routingKey - exchange: - name: example-topic-exchange - type: topic - durable: true - autoDelete: false - vhost: / - bindingVersion: 0.3.0 - multi-payload-queue: + multi-payload-queue_id: address: multi-payload-queue messages: io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto: @@ -114,8 +100,8 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - queue-create: + bindingVersion: 0.4.0 + queue-create_id: address: queue-create messages: io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: @@ -129,8 +115,8 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - queue-delete: + bindingVersion: 0.4.0 + queue-delete_id: address: queue-delete messages: io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: @@ -144,8 +130,26 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - queue-read: + bindingVersion: 0.4.0 + queue-read-id_#_CRUD-topic-exchange-2-id: + address: CRUD-topic-exchange-2 + messages: + io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + bindings: + amqp: + channel: + $ref: "#/channels/queue-read-id" + is: routingKey + name: "#" + exchange: + name: CRUD-topic-exchange-2 + type: topic + durable: true + autoDelete: false + vhost: / + bindingVersion: 0.4.0 + queue-read_id: address: queue-read bindings: amqp: @@ -156,8 +160,26 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 - queue-update: + bindingVersion: 0.4.0 + queue-update-id_#_CRUD-topic-exchange-1-id: + address: CRUD-topic-exchange-1 + messages: + io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: + $ref: "#/components/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + bindings: + amqp: + channel: + $ref: "#/channels/queue-update-id" + is: routingKey + name: "#" + exchange: + name: CRUD-topic-exchange-1 + type: topic + durable: true + autoDelete: false + vhost: / + bindingVersion: 0.4.0 + queue-update_id: address: queue-update bindings: amqp: @@ -168,7 +190,7 @@ channels: exclusive: false autoDelete: false vhost: / - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 components: schemas: HeadersNotDocumented: @@ -263,7 +285,7 @@ components: description: Another payload model bindings: amqp: - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto: headers: $ref: "#/components/schemas/SpringRabbitListenerDefaultHeaders" @@ -275,7 +297,7 @@ components: title: ExamplePayloadDto bindings: amqp: - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 io.github.springwolf.examples.amqp.dtos.GenericPayloadDto: headers: $ref: "#/components/schemas/SpringRabbitListenerDefaultHeaders" @@ -287,61 +309,43 @@ components: title: GenericPayloadDto bindings: amqp: - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 operations: - '#_receive_bindingsRead': - action: receive - channel: - $ref: "#/channels/#" - bindings: - amqp: - expiration: 0 - cc: - - "#" - bindingVersion: 0.3.0 - messages: - - $ref: "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" - '#_receive_bindingsUpdate': + another-queue_id_receive_receiveAnotherPayload: action: receive channel: - $ref: "#/channels/#" + $ref: "#/channels/another-queue_id" bindings: amqp: expiration: 0 - cc: - - "#" - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/#/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" - another-queue_receive_receiveAnotherPayload: + - $ref: "#/channels/another-queue_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id_receive_bindingsExample: action: receive channel: - $ref: "#/channels/another-queue" + $ref: "#/channels/example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id" bindings: amqp: expiration: 0 - cc: - - another-queue - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/another-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - example-queue_receive_receiveExamplePayload: + - $ref: "#/channels/example-bindings-queue-id_example-topic-routing-key_example-topic-exchange-id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + example-queue_id_receive_receiveExamplePayload: action: receive channel: - $ref: "#/channels/example-queue" + $ref: "#/channels/example-queue_id" bindings: amqp: expiration: 0 - cc: - - example-queue - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/example-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" - example-topic-exchange_send_sendMessage: + - $ref: "#/channels/example-queue_id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + example-topic-exchange_id_send_sendMessage: action: send channel: - $ref: "#/channels/example-topic-exchange" - title: example-topic-exchange_send + $ref: "#/channels/example-topic-exchange_id" + title: example-topic-exchange_id_send description: "Custom, optional description defined in the AsyncPublisher annotation" bindings: amqp: @@ -352,55 +356,57 @@ operations: mandatory: false timestamp: false ack: false - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 + messages: + - $ref: "#/channels/example-topic-exchange_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + multi-payload-queue_id_receive_bindingsBeanExample: + action: receive + channel: + $ref: "#/channels/multi-payload-queue_id" + bindings: + amqp: + expiration: 0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/example-topic-exchange/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - example-topic-routing-key_receive_bindingsExample: + - $ref: "#/channels/multi-payload-queue_id/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" + - $ref: "#/channels/multi-payload-queue_id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + queue-create_id_receive_queuesToDeclareCreate: action: receive channel: - $ref: "#/channels/example-topic-routing-key" + $ref: "#/channels/queue-create_id" bindings: amqp: expiration: 0 - cc: - - example-topic-routing-key - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/example-topic-routing-key/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - multi-payload-queue_receive_bindingsBeanExample: + - $ref: "#/channels/queue-create_id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + queue-delete_id_receive_queuesToDeclareDelete: action: receive channel: - $ref: "#/channels/multi-payload-queue" + $ref: "#/channels/queue-delete_id" bindings: amqp: expiration: 0 - cc: - - multi-payload-queue - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.AnotherPayloadDto" - - $ref: "#/channels/multi-payload-queue/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" - queue-create_receive_queuesToDeclareCreate: + - $ref: "#/channels/queue-delete_id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + queue-read-id_#_CRUD-topic-exchange-2-id_receive_bindingsRead: action: receive channel: - $ref: "#/channels/queue-create" + $ref: "#/channels/queue-read-id_#_CRUD-topic-exchange-2-id" bindings: amqp: expiration: 0 - cc: - - queue-create - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/queue-create/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" - queue-delete_receive_queuesToDeclareDelete: + - $ref: "#/channels/queue-read-id_#_CRUD-topic-exchange-2-id/messages/io.github.springwolf.examples.amqp.dtos.ExamplePayloadDto" + queue-update-id_#_CRUD-topic-exchange-1-id_receive_bindingsUpdate: action: receive channel: - $ref: "#/channels/queue-delete" + $ref: "#/channels/queue-update-id_#_CRUD-topic-exchange-1-id" bindings: amqp: expiration: 0 - cc: - - queue-delete - bindingVersion: 0.3.0 + bindingVersion: 0.4.0 messages: - - $ref: "#/channels/queue-delete/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" + - $ref: "#/channels/queue-update-id_#_CRUD-topic-exchange-1-id/messages/io.github.springwolf.examples.amqp.dtos.GenericPayloadDto" From 47a48478ff53e0eb1be364c7639f978bca72013e Mon Sep 17 00:00:00 2001 From: Timon Back Date: Sun, 8 Sep 2024 23:32:27 +0200 Subject: [PATCH 09/10] test(ui): reduce errors in test log --- springwolf-ui/src/app/components/mock-components.spec.ts | 7 +++++-- .../app/components/new/channels/channels.component.spec.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/springwolf-ui/src/app/components/mock-components.spec.ts b/springwolf-ui/src/app/components/mock-components.spec.ts index ae15b5dcc..799779653 100644 --- a/springwolf-ui/src/app/components/mock-components.spec.ts +++ b/springwolf-ui/src/app/components/mock-components.spec.ts @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: Apache-2.0 */ -import { Component, input } from "@angular/core"; +import { Component, input, model } from "@angular/core"; import { Schema } from "../models/schema.model"; import { Operation } from "../models/operation.model"; @@ -36,8 +36,11 @@ export class MockChannelOperationComponent { @Component({ selector: "app-prism-editor", template: "" }) export class MockPrismEditorComponent { - code = input(""); + code = model(""); language = input(""); + readonly = input(false, { + transform: (value: string) => value == "true", + }); } @Component({ selector: "app-schema-new", template: "" }) diff --git a/springwolf-ui/src/app/components/new/channels/channels.component.spec.ts b/springwolf-ui/src/app/components/new/channels/channels.component.spec.ts index 8b1150fb8..3f49afefd 100644 --- a/springwolf-ui/src/app/components/new/channels/channels.component.spec.ts +++ b/springwolf-ui/src/app/components/new/channels/channels.component.spec.ts @@ -7,7 +7,10 @@ import { mockedExampleSchemaMapped, } from "../../../service/mock/mock-asyncapi.service"; import { MaterialModule } from "../../../material.module"; -import { MockChannelOperationComponent } from "../../mock-components.spec"; +import { + MockChannelOperationComponent, + MockPrismEditorComponent, +} from "../../mock-components.spec"; describe("ChannelsNewComponent", () => { beforeEach(async () => { @@ -15,7 +18,7 @@ describe("ChannelsNewComponent", () => { await render(ChannelsNewComponent, { imports: [MaterialModule], - declarations: [MockChannelOperationComponent], + declarations: [MockChannelOperationComponent, MockPrismEditorComponent], providers: [ { provide: AsyncApiService, useValue: mockedAsyncApiService }, ], From 7d3c07c27bd46306b38c154b3957a0625bf09019 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Sun, 8 Sep 2024 23:33:12 +0200 Subject: [PATCH 10/10] feat(amqp): be liberal and handle invalid configurations In case spring-amqp changes how it handles inputs, the library should still try to do its best --- .../scanners/bindings/RabbitListenerUtil.java | 61 ++++++++----------- .../bindings/RabbitListenerUtilTest.java | 50 +++++++++------ 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java index 541f3ff78..0edce4270 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtil.java @@ -46,7 +46,6 @@ *
    • Consumer consumes the message from the queue * */ -// TODO: should this do validation and throw errors when an invalid rabbit configuration is found? @Slf4j public class RabbitListenerUtil { public static final String BINDING_NAME = "amqp"; @@ -60,47 +59,29 @@ public static String getChannelName(RabbitListener annotation, StringValueResolv Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> channelNameFromAnnotationBindings(binding, resolver)); - return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) - .map(resolver::resolveStringValue) - .filter(Objects::nonNull) - .peek(queue -> log.debug("Resolved channel name: {}", queue)) - .findFirst() - .orElseThrow( - () -> new IllegalArgumentException( - "No channel name was found in @RabbitListener annotation (neither in queues nor bindings property)")); + Stream stream = Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames); + return resolveFirstValue(stream, resolver, "channel name"); } public static String getChannelId(RabbitListener annotation, StringValueResolver resolver) { Stream annotationBindingChannelIds = Arrays.stream(annotation.bindings()) .flatMap(binding -> channelIdFromAnnotationBindings(binding, resolver)); - return Stream.concat(streamQueueNames(annotation).map(ReferenceUtil::toValidId), annotationBindingChannelIds) - .map(resolver::resolveStringValue) - .filter(Objects::nonNull) - .peek(queue -> log.debug("Resolved channel id: {}", queue)) - .findFirst() - .orElseThrow( - () -> new IllegalArgumentException( - "No channel id was found in @RabbitListener annotation (neither in queues nor bindings property)")); + Stream stream = + Stream.concat(streamQueueNames(annotation).map(ReferenceUtil::toValidId), annotationBindingChannelIds); + return resolveFirstValue(stream, resolver, "channel id"); } private static String getQueueName(RabbitListener annotation, StringValueResolver resolver) { - Stream annotationBindingChannelNames = Arrays.stream(annotation.bindings()) + Stream annotationBindingQueueNames = Arrays.stream(annotation.bindings()) .flatMap(binding -> Stream.of(binding.value().name())); - return Stream.concat(streamQueueNames(annotation), annotationBindingChannelNames) - .map(resolver::resolveStringValue) - .filter(Objects::nonNull) - .peek(queue -> log.debug("Resolved queue name: {}", queue)) - .findFirst() - .orElseThrow( - () -> new IllegalArgumentException( - "No queue name was found in @RabbitListener annotation (neither in queues nor bindings property)")); + Stream stream = Stream.concat(streamQueueNames(annotation), annotationBindingQueueNames); + return resolveFirstValue(stream, resolver, "queue name"); } private static Stream channelNameFromAnnotationBindings( QueueBinding binding, StringValueResolver resolver) { - String queueName = resolver.resolveStringValue(binding.value().name()); String exchangeName = resolver.resolveStringValue(binding.exchange().name()); String[] routingKeys = binding.key(); @@ -111,15 +92,6 @@ private static Stream channelNameFromAnnotationBindings( return Arrays.stream(routingKeys).map(resolver::resolveStringValue).map(routingKey -> exchangeName); } - private static String exchangeTargetChannelIdFromAnnotationBindings( - RabbitListener annotation, StringValueResolver resolver) { - return Arrays.stream(annotation.bindings()) - .map(binding -> binding.value().name() + "-id") - .map(resolver::resolveStringValue) - .findFirst() - .orElse(null); - } - private static Stream channelIdFromAnnotationBindings(QueueBinding binding, StringValueResolver resolver) { String queueName = resolver.resolveStringValue(binding.value().name()); String exchangeName = resolver.resolveStringValue(binding.exchange().name()); @@ -170,6 +142,14 @@ public static Map buildChannelBinding( return Map.of(BINDING_NAME, channelBinding.build()); } + private static String exchangeTargetChannelIdFromAnnotationBindings( + RabbitListener annotation, StringValueResolver resolver) { + Stream stream = Arrays.stream(annotation.bindings()) + .map(binding -> binding.value().name() + "-id"); + + return resolveFirstValue(stream, resolver, "exchange target channel id"); + } + private static AMQPChannelExchangeProperties buildExchangeProperties( RabbitListener annotation, String exchangeName, RabbitListenerUtilContext context) { @@ -314,4 +294,13 @@ public static Map buildMessageBinding() { // currently the feature to define amqp message binding is not implemented. return Map.of(BINDING_NAME, new AMQPMessageBinding()); } + + private static String resolveFirstValue(Stream values, StringValueResolver resolver, String valueType) { + return values.map(resolver::resolveStringValue) + .filter(Objects::nonNull) + .peek(value -> log.debug("Resolved {}: {}", valueType, value)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No " + valueType + + " was found in @RabbitListener annotation (neither in queues nor bindings property)")); + } } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java index a3b7323c2..54afa5f74 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/springwolf/plugins/amqp/asyncapi/scanners/bindings/RabbitListenerUtilTest.java @@ -14,7 +14,6 @@ import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference; import org.assertj.core.util.Sets; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.amqp.core.TopicExchange; @@ -106,8 +105,10 @@ void buildChannelBinding() { channelBinding.get("amqp")); } + /** + * Technically an invalid configuration as queue will be part of the spring context + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); @@ -148,8 +149,10 @@ void buildOperationBinding() { assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } + /** + * Technically an invalid configuration as queue will be part of the spring context + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesConfiguration.class); @@ -161,7 +164,7 @@ void buildOperationBindingWithEmptyContext() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -197,8 +200,10 @@ void getChannelName() { assertEquals("queue-1", channelName); } + /** + * Technically an invalid configuration as context should be empty + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildChannelBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); @@ -215,9 +220,9 @@ void buildChannelBinding() { .is(AMQPChannelType.QUEUE) .queue(AMQPChannelQueueProperties.builder() .name("queue-1") - .durable(true) - .autoDelete(false) - .exclusive(false) + .durable(false) + .autoDelete(true) + .exclusive(true) .vhost("/") .build()) .build(), @@ -250,8 +255,10 @@ void buildChannelBindingWithEmptyContext() { channelBinding.get("amqp")); } + /** + * Technically an invalid configuration as context should be empty + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildOperationBinding() { // given RabbitListener annotation = getAnnotation(ClassWithQueuesToDeclare.class); @@ -263,7 +270,7 @@ void buildOperationBinding() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("queue-1")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -380,8 +387,10 @@ void buildChannelBindingWithExchangeContext() { channelBinding.get("amqp")); } + /** + * Technically an invalid configuration as queue and exchange will be part of the spring context + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); @@ -396,9 +405,11 @@ void buildChannelBindingWithEmptyContext() { assertEquals( AMQPChannelBinding.builder() .is(AMQPChannelType.ROUTING_KEY) + .name("#") + .channel(ChannelReference.fromChannel("queue-1-id")) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(AMQPChannelExchangeType.TOPIC) + .type(AMQPChannelExchangeType.DIRECT) .durable(true) .autoDelete(false) .build()) @@ -436,8 +447,10 @@ void buildOperationBindingWithExchangeContext() { assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } + /** + * Technically an invalid configuration as queue and exchange will be part of the spring context + */ @Test - @Disabled("TODO: what to do with invalid configuration") void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingConfiguration.class); @@ -449,7 +462,7 @@ void buildOperationBindingWithEmptyContext() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals(AMQPOperationBinding.builder().cc(List.of("")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test @@ -528,7 +541,7 @@ void buildChannelBinding() { } @Test - @Disabled("TODO: what to do with invalid configuration") + // @Disabled("TODO: what to do with invalid configuration") void buildChannelBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); @@ -543,6 +556,8 @@ void buildChannelBindingWithEmptyContext() { assertEquals( AMQPChannelBinding.builder() .is(AMQPChannelType.ROUTING_KEY) + .name("routing-key") + .channel(ChannelReference.fromChannel("queue-1-id")) .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") .type(AMQPChannelExchangeType.DIRECT) @@ -569,7 +584,7 @@ void buildOperationBinding() { } @Test - @Disabled("TODO: what to do with invalid configuration") + // @Disabled("TODO: what to do with invalid configuration") void buildOperationBindingWithEmptyContext() { // given RabbitListener annotation = getAnnotation(ClassWithBindingsAndRoutingKeyConfiguration.class); @@ -581,8 +596,7 @@ void buildOperationBindingWithEmptyContext() { // then assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("amqp"), operationBinding.keySet()); - assertEquals( - AMQPOperationBinding.builder().cc(List.of("routing-key")).build(), operationBinding.get("amqp")); + assertEquals(AMQPOperationBinding.builder().build(), operationBinding.get("amqp")); } @Test