Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: amqp channel name when using exchange #886

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
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) {
return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_");
return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_") + ID_POSTFIX;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ channels:
exclusive: true
autoDelete: false
vhost: /
bindingVersion: 0.3.0
bindingVersion: 0.4.0
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ channels:
durable: true
autoDelete: false
vhost: /
bindingVersion: 0.3.0
bindingVersion: 0.4.0
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ channels:
amqp:
contentEncoding: gzip
messageType: 'user.signup'
bindingVersion: 0.3.0
bindingVersion: 0.4.0
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ operations:
bcc: ['external.audit']
timestamp: true
ack: false
bindingVersion: 0.3.0
bindingVersion: 0.4.0
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +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<T> {
default String getChannelId(T annotation) {
return ReferenceUtil.toValidId(getChannelName(annotation));
}

String getChannelName(T annotation);

Map<String, ChannelBinding> buildChannelBinding(T annotation);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,14 +27,13 @@ public static Map<String, MessageReference> toMessagesMap(Set<MessageObject> mes
return toMessageReferences(messages, aggregator);
}

public static Map<String, MessageReference> toOperationsMessagesMap(
String channelName, Set<MessageObject> messages) {
if (channelName == null || channelName.isBlank()) {
throw new IllegalArgumentException("channelName must not be empty");
public static Map<String, MessageReference> toOperationsMessagesMap(String channelId, Set<MessageObject> messages) {
if (channelId == null || channelId.isBlank()) {
throw new IllegalArgumentException("channelId must not be empty");
}

Function<MessageObject, MessageReference> aggregator = (message) ->
MessageReference.toChannelMessage(ReferenceUtil.toValidId(channelName), message.getMessageId());
Function<MessageObject, MessageReference> aggregator =
(message) -> MessageReference.toChannelMessage(channelId, message.getMessageId());
return toMessageReferences(messages, aggregator);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public Map<String, MessageReference> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package io.github.springwolf.core.asyncapi.scanners.common.operation;

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;
Expand Down Expand Up @@ -30,7 +29,7 @@ public Operation buildOperation(
MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadType, headerSchema);
Map<String, OperationBinding> operationBinding = bindingFactory.buildOperationBinding(annotation);
Map<String, OperationBinding> 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.operations.annotations;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
Expand Down Expand Up @@ -44,8 +43,7 @@ private Stream<Map.Entry<String, Operation>> mapClassToOperation(
Class<?> component, Set<MethodAndAnnotation<MethodAnnotation>> annotatedMethods) {
ClassAnnotation classAnnotation = AnnotationUtil.findFirstAnnotationOrThrow(classAnnotationClass, component);

String channelName = bindingFactory.getChannelName(classAnnotation);
String channelId = ReferenceUtil.toValidId(channelName);
String channelId = bindingFactory.getChannelId(classAnnotation);
String operationId =
StringUtils.joinWith("_", channelId, OperationAction.RECEIVE.type, component.getSimpleName());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.operations.annotations;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
Expand Down Expand Up @@ -44,8 +43,7 @@ public Stream<Map.Entry<String, Operation>> scan(Class<?> clazz) {
private Map.Entry<String, Operation> mapMethodToOperation(MethodAndAnnotation<MethodAnnotation> method) {
MethodAnnotation annotation = AnnotationUtil.findFirstAnnotationOrThrow(methodAnnotationClass, method.method());

String channelName = bindingFactory.getChannelName(annotation);
String channelId = ReferenceUtil.toValidId(channelName);
String channelId = bindingFactory.getChannelId(annotation);
String operationId = StringUtils.joinWith(
"_", channelId, OperationAction.RECEIVE.type, method.method().getName());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SpringAnnotationOperationServiceTest {
@BeforeEach
void setUp() {
// when
when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_ID);
when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID);
doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void scan() {
// then
assertThat(actualOperations).hasSize(1);
assertThat(actualOperations)
.containsExactlyEntriesOf(Map.of("test-channel_send_ClassWithListenerAnnotation", operation));
.containsExactlyEntriesOf(Map.of("test-channel_id_send_ClassWithListenerAnnotation", operation));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void scan_componentOperationHasListenerMethod() {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// then
assertThat(actualOperations).containsExactly(Map.entry("test-channel_send_methodWithAnnotation", operation));
assertThat(actualOperations).containsExactly(Map.entry("test-channel_id_send_methodWithAnnotation", operation));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ class SpringAnnotationClassLevelOperationsScannerTest {
springAnnotationOperationsService,
List.of(operationCustomizer));

private static final String CHANNEL_NAME = "test-channel";
private static final String CHANNEL_ID = "test-channel_id";

private static final Map<String, MessageBinding> defaultMessageBinding =
Map.of("protocol", new AMQPMessageBinding());

@BeforeEach
void setUp() {
when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_NAME);
when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID);
}

@Test
Expand All @@ -56,7 +56,7 @@ void scan() {
scanner.scan(ClassWithTestListenerAnnotation.class).toList();

// then
String operationName = CHANNEL_NAME + "_receive_ClassWithTestListenerAnnotation";
String operationName = CHANNEL_ID + "_receive_ClassWithTestListenerAnnotation";
assertThat(operations).containsExactly(Map.entry(operationName, operation));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ class SpringAnnotationMethodLevelOperationsScannerTest {
springAnnotationOperationService,
List.of(operationCustomizer));

private static final String CHANNEL_NAME = "test-channel";
private static final String CHANNEL_ID = "test-channel_id";

@BeforeEach
void setUp() {
when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL_NAME);
when(bindingFactory.getChannelId(any())).thenReturn(CHANNEL_ID);
}

@Test
Expand All @@ -56,7 +56,7 @@ void scan_componentHasTestListenerMethods() {
scanner.scan(ClassWithTestListenerAnnotation.class).toList();

// then
String operationName = CHANNEL_NAME + "_receive_methodWithAnnotation";
String operationName = CHANNEL_ID + "_receive_methodWithAnnotation";
assertThat(operations).containsExactly(Map.entry(operationName, operation));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ void asyncListenerAnnotationIsFound() {
assertThat(asyncAPI).isNotNull();

assertThat(asyncAPI.getChannels().keySet())
.containsExactlyInAnyOrder("listener-channel", "listener-class-channel");
assertThat(asyncAPI.getChannels().get("listener-channel").getMessages())
.containsExactlyInAnyOrder("listener-channel_id", "listener-class-channel_id");
assertThat(asyncAPI.getChannels().get("listener-channel_id").getMessages())
.containsOnlyKeys(
"java.lang.String",
"io.github.springwolf.core.integrationtests.application.listener.ListenerApplication$Foo");
assertThat(asyncAPI.getChannels().get("listener-class-channel").getMessages())
assertThat(asyncAPI.getChannels().get("listener-class-channel_id").getMessages())
.containsOnlyKeys("java.lang.Integer");
assertThat(asyncAPI.getOperations())
.containsOnlyKeys(
"listener-channel_receive_listen",
"listener-channel_receive_listen2",
"listener-channel_receive_listen3",
"listener-channel_receive_listen4",
"listener-class-channel_receive_ClassListener");
"listener-channel_id_receive_listen",
"listener-channel_id_receive_listen2",
"listener-channel_id_receive_listen3",
"listener-channel_id_receive_listen4",
"listener-class-channel_id_receive_ClassListener");
assertThat(asyncAPI.getComponents().getMessages())
.containsOnlyKeys(
"java.lang.String",
Expand Down Expand Up @@ -106,20 +106,20 @@ void asyncPublisherAnnotationIsFound() {
assertThat(asyncAPI).isNotNull();

assertThat(asyncAPI.getChannels().keySet())
.containsExactlyInAnyOrder("publisher-channel", "publisher-class-channel");
assertThat(asyncAPI.getChannels().get("publisher-channel").getMessages())
.containsExactlyInAnyOrder("publisher-channel_id", "publisher-class-channel_id");
assertThat(asyncAPI.getChannels().get("publisher-channel_id").getMessages())
.containsOnlyKeys(
"java.lang.String",
"io.github.springwolf.core.integrationtests.application.listener.ListenerApplication$Foo");
assertThat(asyncAPI.getChannels().get("publisher-class-channel").getMessages())
assertThat(asyncAPI.getChannels().get("publisher-class-channel_id").getMessages())
.containsOnlyKeys("java.lang.Integer");
assertThat(asyncAPI.getOperations())
.containsOnlyKeys(
"publisher-channel_send_publish",
"publisher-channel_send_publish2",
"publisher-channel_send_publish3",
"publisher-channel_send_publish4",
"publisher-class-channel_send_ClassPublisher");
"publisher-channel_id_send_publish",
"publisher-channel_id_send_publish2",
"publisher-channel_id_send_publish3",
"publisher-channel_id_send_publish4",
"publisher-class-channel_id_send_ClassPublisher");
assertThat(asyncAPI.getComponents().getMessages())
.containsOnlyKeys(
"java.lang.String",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down
Loading
Loading