diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageObject.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageObject.java index b50246464..d632512cb 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageObject.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageObject.java @@ -118,4 +118,14 @@ public class MessageObject extends ExtendableObject implements Message { */ @JsonProperty(value = "traits") private List traits; + + /* + * Override the getMessageId to guarantee that there's always a value. Defaults to 'name' + */ + public String getMessageId() { + if (messageId == null) { + return this.name; + } + return messageId; + } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/MessageHelper.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/MessageHelper.java index a75a58c48..4137561b1 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/MessageHelper.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/MessageHelper.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import lombok.extern.slf4j.Slf4j; @@ -8,28 +9,28 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @Slf4j public class MessageHelper { - private static final String ONE_OF = "oneOf"; - private static final Comparator byMessageName = Comparator.comparing(MessageObject::getName); private static final Supplier> messageSupplier = () -> new TreeSet<>(byMessageName); - public static Object toMessageObjectOrComposition(Set messages) { - return switch (messages.size()) { - case 0 -> throw new IllegalArgumentException("messages must not be empty"); - case 1 -> messages.toArray()[0]; - default -> Map.of( - ONE_OF, new ArrayList<>(messages.stream().collect(Collectors.toCollection(messageSupplier)))); - }; + private MessageHelper() {} + + public static Map toMessagesMap(Set messages) { + if (messages.isEmpty()) { + throw new IllegalArgumentException("messages must not be empty"); + } + + return new ArrayList<>(messages.stream().collect(Collectors.toCollection(messageSupplier))) + .stream().collect(Collectors.toMap(MessageObject::getMessageId, Function.identity())); } @SuppressWarnings("unchecked") @@ -38,10 +39,11 @@ public static Set messageObjectToSet(Object messageObject) { return new HashSet<>(Collections.singletonList(message)); } - if (messageObject instanceof Map) { - List messages = ((Map>) messageObject).get(ONE_OF); - return new HashSet<>(messages); - } + // FIXME + // if (messageObject instanceof Map) { + // List messages = ((Map>) messageObject).get(ONE_OF); + // return new HashSet<>(messages); + // } log.warn( "Message object must contain either a Message or a Map, but contained: {}", diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScanner.java index 68fc978e2..dbca4a988 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScanner.java @@ -7,10 +7,14 @@ import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersBuilder; import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; -import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageHeaders; import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; -import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaReference; import io.github.stavshamir.springwolf.schemas.SchemasService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,7 +28,7 @@ import java.util.Set; import java.util.stream.Stream; -import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessageObjectOrComposition; +import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessagesMap; import static java.util.stream.Collectors.toSet; @RequiredArgsConstructor @@ -75,20 +79,18 @@ private Stream> mapClassToChannel(Class comp } String channelName = bindingFactory.getChannelName(classAnnotation); - String operationId = channelName + "_publish_" + component.getSimpleName(); - ChannelObject channelItem = buildChannelItem(classAnnotation, operationId, annotatedMethods); + ChannelObject channelItem = buildChannelItem(classAnnotation, annotatedMethods); return Stream.of(Map.entry(channelName, channelItem)); } - private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, String operationId, Set methods) { - Object message = buildMessageObject(classAnnotation, methods); - Operation operation = buildOperation(classAnnotation, operationId, message); - return buildChannelItem(classAnnotation, operation); + private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Set methods) { + var messages = buildMessages(classAnnotation, methods); + return buildChannelItem(classAnnotation, messages); } - private Object buildMessageObject(ClassAnnotation classAnnotation, Set methods) { + private Map buildMessages(ClassAnnotation classAnnotation, Set methods) { Set messages = methods.stream() .map((Method method) -> { Class payloadType = payloadClassExtractor.extractFrom(method); @@ -96,7 +98,7 @@ private Object buildMessageObject(ClassAnnotation classAnnotation, Set m }) .collect(toSet()); - return toMessageObjectOrComposition(messages); + return toMessagesMap(messages); } private MessageObject buildMessage(ClassAnnotation classAnnotation, Class payloadType) { @@ -104,33 +106,24 @@ private MessageObject buildMessage(ClassAnnotation classAnnotation, Class pay String modelName = schemasService.register(payloadType); String headerModelName = schemasService.register(asyncHeadersBuilder.buildHeaders(payloadType)); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(modelName)) + .build()); + return MessageObject.builder() + .messageId(payloadType.getName()) .name(payloadType.getName()) .title(payloadType.getSimpleName()) .description(null) - // .payload(PayloadReference.fromModelName(modelName)) FIXME - // .headers(HeaderReference.fromModelName(headerModelName)) FIXME + .payload(payload) + .headers(MessageHeaders.of(MessageReference.fromSchema(headerModelName))) .bindings(messageBinding) .build(); } - private Operation buildOperation(ClassAnnotation classAnnotation, String operationTitle, Object message) { - Map operationBinding = bindingFactory.buildOperationBinding(classAnnotation); - Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - - return Operation.builder() - .description("Auto-generated description") - .title(operationTitle) - // .message(message) - .bindings(opBinding) - .build(); - } - - private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Operation operation) { + private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Map messages) { Map channelBinding = bindingFactory.buildChannelBinding(classAnnotation); Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; - return ChannelObject.builder() - .bindings(chBinding) /*.publish(operation) FIXME*/ - .build(); + return ChannelObject.builder().bindings(chBinding).messages(messages).build(); } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/MessageHelperTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/MessageHelperTest.java index 2810e3dca..4fd03f4aa 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/MessageHelperTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/MessageHelperTest.java @@ -1,17 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import org.junit.jupiter.api.Test; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.messageObjectToSet; -import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessageObjectOrComposition; +import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessagesMap; +import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -19,32 +18,31 @@ class MessageHelperTest { @Test void toMessageObjectOrComposition_emptySet() { - assertThatThrownBy(() -> toMessageObjectOrComposition(Collections.emptySet())) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> toMessagesMap(Collections.emptySet())).isInstanceOf(IllegalArgumentException.class); } @Test - void toMessageObjectOrComposition_oneMessage() { + void toMessagesMap_oneMessage() { MessageObject message = MessageObject.builder().name("foo").build(); - Object asObject = toMessageObjectOrComposition(Set.of(message)); + var messagesMap = toMessagesMap(Set.of(message)); - assertThat(asObject).isInstanceOf(Message.class).isEqualTo(message); + assertThat(messagesMap).containsExactlyInAnyOrderEntriesOf(Map.of("foo", message)); } @Test - void toMessageObjectOrComposition_multipleMessages() { + void toMessagesMap_multipleMessages() { MessageObject message1 = MessageObject.builder().name("foo").build(); MessageObject message2 = MessageObject.builder().name("bar").build(); - Object asObject = toMessageObjectOrComposition(Set.of(message1, message2)); + var messages = toMessagesMap(Set.of(message1, message2)); - assertThat(asObject).isInstanceOf(Map.class).isEqualTo(Map.of("oneOf", List.of(message2, message1))); + assertThat(messages).containsExactlyEntriesOf(Map.of("bar", message2, "foo", message1)); } @Test - void toMessageObjectOrComposition_multipleMessages_remove_duplicates() { + void toMessagesMap_multipleMessages_remove_duplicates() { MessageObject message1 = MessageObject.builder() .name("foo") .description("This is message 1") @@ -60,17 +58,17 @@ void toMessageObjectOrComposition_multipleMessages_remove_duplicates() { .description("This is message 3, but in essence the same payload type as message 2") .build(); - Object asObject = toMessageObjectOrComposition(Set.of(message1, message2, message3)); + var messages = toMessagesMap(Set.of(message1, message2, message3)); - Map> oneOfMap = (Map>) asObject; - assertThat(oneOfMap).hasSize(1); - List deduplicatedMessageList = oneOfMap.get("oneOf"); - // we do not have any guarantee wether message2 or message3 won the deduplication. - assertThat(deduplicatedMessageList).hasSize(2).contains(message1).containsAnyOf(message2, message3); + // we do not have any guarantee whether message2 or message3 won the deduplication. + assertThat(messages) + .hasSize(2) + .containsValue(message1) + .containsAnyOf(entry("bar", message2), entry("bar", message3)); } @Test - void toMessageObjectOrComposition_multipleMessages_should_not_break_deep_equals() { + void toMessagesMap_multipleMessages_should_not_break_deep_equals() { MessageObject actualMessage1 = MessageObject.builder() .name("foo") .description("This is actual message 1") @@ -81,7 +79,7 @@ void toMessageObjectOrComposition_multipleMessages_should_not_break_deep_equals( .description("This is actual message 2") .build(); - Object actualObject = toMessageObjectOrComposition(Set.of(actualMessage1, actualMessage2)); + Object actualObject = toMessagesMap(Set.of(actualMessage1, actualMessage2)); MessageObject expectedMessage1 = MessageObject.builder() .name("foo") @@ -93,7 +91,7 @@ void toMessageObjectOrComposition_multipleMessages_should_not_break_deep_equals( .description("This is expected message 2") .build(); - Object expectedObject = toMessageObjectOrComposition(Set.of(expectedMessage1, expectedMessage2)); + Object expectedObject = toMessagesMap(Set.of(expectedMessage1, expectedMessage2)); assertThat(actualObject).isNotEqualTo(expectedObject); } @@ -110,7 +108,7 @@ void messageObjectToSet_notAMessageOrAMap() { @Test void messageObjectToSet_Message() { MessageObject message = MessageObject.builder().name("foo").build(); - Object asObject = toMessageObjectOrComposition(Set.of(message)); + Object asObject = toMessagesMap(Set.of(message)); Set messages = messageObjectToSet(asObject); @@ -123,7 +121,7 @@ void messageObjectToSet_SetOfMessage() { MessageObject message2 = MessageObject.builder().name("bar").build(); - Object asObject = toMessageObjectOrComposition(Set.of(message1, message2)); + Object asObject = toMessagesMap(Set.of(message1, message2)); Set messages = messageObjectToSet(asObject); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMergerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMergerTest.java index 5e755e09a..99a2d8403 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMergerTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMergerTest.java @@ -148,7 +148,7 @@ void shouldMergeDifferentMessageForSameOperation() { // then expectedMessage only includes message1 and message2. // Message3 is not included as it is identical in terms of payload type (Message#name) to message 2 - Object expectedMessages = MessageHelper.toMessageObjectOrComposition(Set.of(message1, message2)); + Object expectedMessages = MessageHelper.toMessagesMap(Set.of(message1, message2)); assertThat(mergedChannels).hasSize(1); // .hasEntrySatisfying(channelName, it -> { // assertThat(it.getPublish()) @@ -188,7 +188,7 @@ void shouldUseOtherMessageIfFirstMessageIsMissing() { Arrays.asList(Map.entry(channelName, publisherChannel1), Map.entry(channelName, publisherChannel2))); // then expectedMessage message2 - Object expectedMessages = MessageHelper.toMessageObjectOrComposition(Set.of(message2)); + Object expectedMessages = MessageHelper.toMessagesMap(Set.of(message2)); assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { // assertThat(it.getPublish()) FIXME // .isEqualTo(Operation.builder()