diff --git a/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/stavshamir/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmount.java b/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/stavshamir/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmount.java index ea6d52de3..e355a176b 100644 --- a/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/stavshamir/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmount.java +++ b/springwolf-add-ons/springwolf-common-model-converters/src/main/java/io/github/stavshamir/springwolf/addons/common_model_converters/converters/monetaryamount/MonetaryAmount.java @@ -10,7 +10,7 @@ class MonetaryAmount { @JsonProperty("amount") - @Schema(example = "99.99") + @Schema(example = "99.99", minimum = "0.1", exclusiveMinimum = true) private BigDecimal amount; @JsonProperty("currency") diff --git a/springwolf-add-ons/springwolf-generic-binding/build.gradle b/springwolf-add-ons/springwolf-generic-binding/build.gradle index 966e5b829..7d8dba5fd 100644 --- a/springwolf-add-ons/springwolf-generic-binding/build.gradle +++ b/springwolf-add-ons/springwolf-generic-binding/build.gradle @@ -8,8 +8,8 @@ plugins { dependencies { api project(":springwolf-core") + api project(":springwolf-asyncapi") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation "org.springframework:spring-context" @@ -17,6 +17,8 @@ dependencies { annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" diff --git a/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/processor/AsyncGenericOperationBindingProcessor.java b/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/processor/AsyncGenericOperationBindingProcessor.java index 14314f755..0d721d5b6 100644 --- a/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/processor/AsyncGenericOperationBindingProcessor.java +++ b/springwolf-add-ons/springwolf-generic-binding/src/main/java/io/github/stavshamir/springwolf/addons/generic_binding/annotation/processor/AsyncGenericOperationBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.addons.generic_binding.annotation.processor; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.addons.generic_binding.annotation.AsyncGenericOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.AbstractOperationBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; import java.util.HashMap; import java.util.Map; diff --git a/springwolf-add-ons/springwolf-json-schema/build.gradle b/springwolf-add-ons/springwolf-json-schema/build.gradle index 42a61851e..0d3795f8c 100644 --- a/springwolf-add-ons/springwolf-json-schema/build.gradle +++ b/springwolf-add-ons/springwolf-json-schema/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation "org.springframework:spring-context" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" diff --git a/springwolf-asyncapi/README.md b/springwolf-asyncapi/README.md index 71363eaab..c3765402b 100644 --- a/springwolf-asyncapi/README.md +++ b/springwolf-asyncapi/README.md @@ -51,6 +51,7 @@ dependencies { See https://github.com/asyncapi/converter-js#conversion-2xx-to-3xx + # Contributions Any contributions are welcome, including, but not limited to: diff --git a/springwolf-asyncapi/build.gradle b/springwolf-asyncapi/build.gradle index 85bfce7e0..3a3cf444d 100644 --- a/springwolf-asyncapi/build.gradle +++ b/springwolf-asyncapi/build.gradle @@ -47,7 +47,7 @@ publishing { mavenJava(MavenPublication) { pom { name = 'springwolf-asyncapi' - description = 'AsyncAPI schema generator' + description = 'Springwolf implementation of the AsyncApi specification' } } } diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSChannelBindingQueue.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSChannelBindingQueue.java index c928b2fe4..1ac3dee36 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSChannelBindingQueue.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSChannelBindingQueue.java @@ -25,7 +25,7 @@ public class SQSChannelBindingQueue { * identifier should be the one in this field. */ @NotNull - @JsonProperty("queue") + @JsonProperty("name") private String name; /** diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSOperationBinding.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSOperationBinding.java index d7e278a50..d3995b0fa 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSOperationBinding.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/sqs/SQSOperationBinding.java @@ -10,6 +10,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.List; + /** * SQS Point-To-Point *

@@ -32,7 +34,7 @@ public class SQSOperationBinding extends OperationBinding { */ @NotNull @JsonProperty("queues") - private SQSChannelBindingQueue queues; + private List queues; /** * Optional, defaults to latest. The version of this binding. diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObject.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObject.java index 18688d0cf..957f05ba7 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObject.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObject.java @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.v3.model.channel; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; import io.github.stavshamir.springwolf.asyncapi.v3.model.ExtendableObject; @@ -29,14 +28,6 @@ @EqualsAndHashCode(callSuper = true) public class ChannelObject extends ExtendableObject implements Channel { - /** - * An identifier for the described channel. The channelId value is case-sensitive. Tools and libraries MAY use the - * channelId to uniquely identify a channel, therefore, it is RECOMMENDED to follow common programming naming - * conventions. - */ - @JsonIgnore - private String channelId; - /** * An optional string representation of this channel's address. The address is typically the "topic name", * "routing key", "event type", or "path". When null or absent, it MUST be interpreted as unknown. This is useful diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelReference.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelReference.java index b3051777b..5cc96914d 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelReference.java @@ -24,17 +24,4 @@ public String getRef() { public static ChannelReference fromChannel(String channelName) { return new ChannelReference("#/channels/" + channelName); } - - /** - * Convenient Builder to create a Channel reference to an existing Channel - * @param channel Channel to create the reference to. This Channel MUST have a 'channelId' field - * @return a Channel with the 'ref' field pointing to "#/channels/{channelId" - */ - public static ChannelReference fromChannel(ChannelObject channel) { - var channelId = channel.getChannelId(); - if (channelId == null) { - throw new IllegalArgumentException("The channel must have a 'channelId' defined"); - } - return new ChannelReference("#/channels/" + channelId); - } } diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageHeaders.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageHeaders.java index bb5c864bf..0740a5405 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageHeaders.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageHeaders.java @@ -5,10 +5,12 @@ import io.github.stavshamir.springwolf.asyncapi.v3.jackson.model.channel.message.MessageHeadersSerializer; import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema; import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; +import lombok.EqualsAndHashCode; import lombok.Getter; @Getter @JsonSerialize(using = MessageHeadersSerializer.class) +@EqualsAndHashCode public class MessageHeaders { private MultiFormatSchema multiFormatSchema; private SchemaObject schema; 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-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java index f43f9c696..04125cada 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessagePayload.java @@ -5,10 +5,12 @@ import io.github.stavshamir.springwolf.asyncapi.v3.jackson.model.channel.message.MessagePayloadSerializer; import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema; import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; +import lombok.EqualsAndHashCode; import lombok.Getter; @Getter @JsonSerialize(using = MessagePayloadSerializer.class) +@EqualsAndHashCode public class MessagePayload { private MultiFormatSchema multiFormatSchema; private SchemaObject schema; diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageReference.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageReference.java index e4e299b7b..e1148773d 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageReference.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/message/MessageReference.java @@ -25,22 +25,27 @@ public String getRef() { /** * Convenient Builder to create a Message reference to an existing Message - * @param message Message to create the reference to. This Message MUST have a 'messageId' field - * @return a Message with the 'ref' field pointing to "#/components/messages/{messageId" + * + * @param message Message to create the reference to. This Message MUST have a 'messageName' field + * @return a Message with the 'ref' field pointing to "#/components/messages/{messageName}" */ - public static MessageReference fromMessage(MessageObject message) { - var messageId = message.getMessageId(); - if (messageId == null) { - throw new IllegalArgumentException("The message must have a 'messageId' defined"); - } - return new MessageReference("#/components/messages/" + messageId); + public static MessageReference toComponentMessage(MessageObject message) { + return toComponentMessage(message.getName()); } - public static MessageReference fromMessage(String messageName) { + public static MessageReference toComponentMessage(String messageName) { return new MessageReference("#/components/messages/" + messageName); } - public static MessageReference fromSchema(String schemaName) { + public static MessageReference toChannelMessage(String channelName, MessageObject message) { + return new MessageReference("#/channels/" + channelName + "/messages/" + message.getName()); + } + + public static MessageReference toChannelMessage(String channelName, String messageName) { + return new MessageReference("#/channels/" + channelName + "/messages/" + messageName); + } + + public static MessageReference toSchema(String schemaName) { return new MessageReference("#/components/schemas/" + schemaName); } } diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaObject.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaObject.java index abc863cc7..80d1c51e6 100644 --- a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaObject.java +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaObject.java @@ -58,7 +58,7 @@ public class SchemaObject extends ExtendableObject implements Schema { private String type; @JsonProperty(value = "properties") - private Map properties; + private Map properties; /** * CommonMark syntax can be used for rich text representation. diff --git a/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaType.java b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaType.java new file mode 100644 index 000000000..16e50fbcc --- /dev/null +++ b/springwolf-asyncapi/src/main/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaType.java @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.v3.model.schema; + +public class SchemaType { + public static final String NULL = "null"; + public static final String BOOLEAN = "boolean"; + public static final String OBJECT = "object"; + public static final String ARRAY = "array"; + public static final String NUMBER = "number"; + public static final String STRING = "string"; + public static final String INTEGER = "integer"; + + private SchemaType() {} +} diff --git a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/amqp/AMQPBindingTest.java b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/amqp/AMQPBindingTest.java index d46707381..916d6ed6b 100644 --- a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/amqp/AMQPBindingTest.java +++ b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/bindings/amqp/AMQPBindingTest.java @@ -20,7 +20,7 @@ class AMQPBindingTest { private static final DefaultAsyncApiSerializer serializer = new DefaultAsyncApiSerializer(); @Test - void shouldSerializeAMQPChannelBinding() throws IOException { + void shouldSerializeAMQPChannelBindingQueue() throws IOException { var asyncapi = AsyncAPI.builder() .channels(Map.of( @@ -30,13 +30,36 @@ void shouldSerializeAMQPChannelBinding() throws IOException { .bindings(Map.of( "amqp", AMQPChannelBinding.builder() - .is(AMQPChannelType.ROUTING_KEY) + .is(AMQPChannelType.QUEUE) .queue(AMQPChannelQueueProperties.builder() .name("my-queue-name") .durable(true) .exclusive(true) .autoDelete(false) .build()) + .build())) + .build())) + .build(); + + // Uses https://github.com/asyncapi/bindings/blob/master/amqp/README.md#example + var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel-queue.yaml"); + assertThatJson(serializer.toJsonString(asyncapi)) + .whenIgnoringPaths("asyncapi", "operations") + .isEqualTo(example); + } + + @Test + void shouldSerializeAMQPChannelBindingRouting() throws IOException { + + var asyncapi = AsyncAPI.builder() + .channels(Map.of( + "userSignup", + ChannelObject.builder() + .address("user/signup") + .bindings(Map.of( + "amqp", + AMQPChannelBinding.builder() + .is(AMQPChannelType.ROUTING_KEY) .exchange(AMQPChannelExchangeProperties.builder() .name("myExchange") .type(AMQPChannelExchangeType.TOPIC) @@ -48,7 +71,7 @@ void shouldSerializeAMQPChannelBinding() throws IOException { .build(); // Uses https://github.com/asyncapi/bindings/blob/master/amqp/README.md#example - var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel.yaml"); + var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel-routing.yaml"); assertThatJson(serializer.toJsonString(asyncapi)) .whenIgnoringPaths("asyncapi", "operations") .isEqualTo(example); diff --git a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/AsyncAPITest.java b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/AsyncAPITest.java index 0f230b6b5..006ed5b5e 100644 --- a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/AsyncAPITest.java +++ b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/AsyncAPITest.java @@ -57,9 +57,8 @@ void shouldCreateSimpleAsyncAPI() throws IOException { .build(); var channelUserSignedup = ChannelObject.builder() - .channelId("userSignedup") .address("user/signedup") - .messages(Map.of(userSignUpMessage.getMessageId(), MessageReference.fromMessage(userSignUpMessage))) + .messages(Map.of(userSignUpMessage.getMessageId(), MessageReference.toComponentMessage("UserSignedUp"))) .build(); AsyncAPI asyncAPI = AsyncAPI.builder() @@ -68,14 +67,14 @@ void shouldCreateSimpleAsyncAPI() throws IOException { .version("1.0.0") .description("This service is in charge of processing user signups") .build()) - .channels(Map.of(channelUserSignedup.getChannelId(), channelUserSignedup)) + .channels(Map.of("userSignedup", channelUserSignedup)) .operations(Map.of( "sendUserSignedup", Operation.builder() .action(OperationAction.SEND) - .channel(ChannelReference.fromChannel(channelUserSignedup)) - .messages( - List.of(new MessageReference("#/channels/userSignedup/messages/UserSignedUp"))) + .channel(ChannelReference.fromChannel("userSignedup")) + .messages(List.of(MessageReference.toChannelMessage( + "userSignedup", userSignUpMessage.getMessageId()))) .build())) .components(Components.builder() .messages(Map.of(userSignUpMessage.getMessageId(), userSignUpMessage)) @@ -98,7 +97,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(MessageTrait.builder() .ref("#/components/messageTraits/commonHeaders") .build())) - .payload(MessagePayload.of(MessageReference.fromSchema("lightMeasuredPayload"))) + .payload(MessagePayload.of(MessageReference.toSchema("lightMeasuredPayload"))) .build(); var turnOnOffMessage = MessageObject.builder() @@ -109,7 +108,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(MessageTrait.builder() .ref("#/components/messageTraits/commonHeaders") .build())) - .payload(MessagePayload.of(MessageReference.fromSchema("turnOnOffPayload"))) + .payload(MessagePayload.of(MessageReference.toSchema("turnOnOffPayload"))) .build(); var dimLightMessage = MessageObject.builder() @@ -120,7 +119,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(MessageTrait.builder() .ref("#/components/messageTraits/commonHeaders") .build())) - .payload(MessagePayload.of(MessageReference.fromSchema("dimLightPayload"))) + .payload(MessagePayload.of(MessageReference.toSchema("dimLightPayload"))) .build(); AsyncAPI asyncAPI = AsyncAPI.builder() @@ -190,7 +189,8 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { "lightingMeasured", ChannelObject.builder() .address("smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured") - .messages(Map.of("lightMeasured", MessageReference.fromMessage(lightMeasuredMessage))) + .messages(Map.of( + "lightMeasured", MessageReference.toComponentMessage(lightMeasuredMessage))) .description("The topic on which measured values may be produced and consumed.") .parameters(Map.of( "streetlightId", @@ -201,7 +201,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { "lightTurnOn", ChannelObject.builder() .address("smartylighting.streetlights.1.0.action.{streetlightId}.turn.on") - .messages(Map.of("turnOn", MessageReference.fromMessage(turnOnOffMessage))) + .messages(Map.of("turnOn", MessageReference.toComponentMessage(turnOnOffMessage))) .parameters(Map.of( "streetlightId", ChannelParameter.builder() @@ -211,7 +211,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { "lightTurnOff", ChannelObject.builder() .address("smartylighting.streetlights.1.0.action.{streetlightId}.turn.off") - .messages(Map.of("turnOff", MessageReference.fromMessage(turnOnOffMessage))) + .messages(Map.of("turnOff", MessageReference.toComponentMessage(turnOnOffMessage))) .parameters(Map.of( "streetlightId", ChannelParameter.builder() @@ -221,7 +221,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { "lightsDim", ChannelObject.builder() .address("smartylighting.streetlights.1.0.action.{streetlightId}.dim") - .messages(Map.of("dimLight", MessageReference.fromMessage(dimLightMessage))) + .messages(Map.of("dimLight", MessageReference.toComponentMessage(dimLightMessage))) .parameters(Map.of( "streetlightId", ChannelParameter.builder() @@ -239,8 +239,8 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(OperationTraits.builder() .ref("#/components/operationTraits/kafka") .build())) - .messages(List.of( - new MessageReference("#/channels/lightingMeasured/messages/lightMeasured"))) + .messages( + List.of(MessageReference.toChannelMessage("lightingMeasured", "lightMeasured"))) .build(), "turnOn", Operation.builder() @@ -251,7 +251,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(OperationTraits.builder() .ref("#/components/operationTraits/kafka") .build())) - .messages(List.of(new MessageReference("#/channels/lightTurnOn/messages/turnOn"))) + .messages(List.of(MessageReference.toChannelMessage("lightTurnOn", "turnOn"))) .build(), "turnOff", Operation.builder() @@ -262,7 +262,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(OperationTraits.builder() .ref("#/components/operationTraits/kafka") .build())) - .messages(List.of(new MessageReference("#/channels/lightTurnOff/messages/turnOff"))) + .messages(List.of(MessageReference.toChannelMessage("lightTurnOff", "turnOff"))) .build(), "dimLight", Operation.builder() @@ -273,7 +273,7 @@ void shouldCreateStreetlightsKafkaAsyncAPI() throws IOException { .traits(List.of(OperationTraits.builder() .ref("#/components/operationTraits/kafka") .build())) - .messages(List.of(new MessageReference("#/channels/lightsDim/messages/dimLight"))) + .messages(List.of(MessageReference.toChannelMessage("lightsDim", "dimLight"))) .build())) .components(Components.builder() .messages(Map.of( diff --git a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObjectTest.java b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObjectTest.java index 361a5b2a0..9de45680f 100644 --- a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObjectTest.java +++ b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/channel/ChannelObjectTest.java @@ -28,9 +28,9 @@ void shouldSerializeChannelObject() throws IOException { .description("This channel is used to exchange messages about user events.") .messages(Map.of( "userSignedUp", - MessageReference.fromMessage("userSignedUp"), + MessageReference.toComponentMessage("userSignedUp"), "userCompletedOrder", - MessageReference.fromMessage("userCompletedOrder"))) + MessageReference.toComponentMessage("userCompletedOrder"))) .parameters(Map.of( "userId", ChannelParameter.builder() diff --git a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/operation/OperationTest.java b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/operation/OperationTest.java index c7965e172..6162e84e6 100644 --- a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/operation/OperationTest.java +++ b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/operation/OperationTest.java @@ -21,6 +21,7 @@ class OperationTest { @Test void shouldSerializeOperation() throws IOException { + MessageReference userSignedUpReply = MessageReference.toComponentMessage("userSignedUpReply"); var operation = Operation.builder() .title("User sign up") .summary("Action to sign a user up.") @@ -37,7 +38,7 @@ void shouldSerializeOperation() throws IOException { .traits(List.of(OperationTraits.builder() .ref("#/components/operationTraits/kafka") .build())) - .messages(List.of(MessageReference.fromMessage("userSignedUp"))) + .messages(List.of(MessageReference.toComponentMessage("userSignedUp"))) .reply(OperationReply.builder() .address(OperationReplyAddress.builder() .location("$message.header#/replyTo") @@ -45,7 +46,7 @@ void shouldSerializeOperation() throws IOException { .channel(ChannelReference.builder() .ref("#/channels/userSignupReply") .build()) - .messages(List.of(MessageReference.fromMessage("userSignedUpReply"))) + .messages(List.of(userSignedUpReply)) .build()) .build(); diff --git a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaTest.java b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaTest.java index e1161cd50..e2d81b7ca 100644 --- a/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaTest.java +++ b/springwolf-asyncapi/src/test/java/io/github/stavshamir/springwolf/asyncapi/v3/model/schema/SchemaTest.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.github.stavshamir.springwolf.asyncapi.v3.jackson.DefaultAsyncApiSerializer; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.List; @@ -150,14 +149,12 @@ void shouldSerializeModelWithExample() throws JsonProcessingException { } @Test - @Disabled("MODEL WITH BOOLEAN SCHEMAS is not supported yet") - // FIXME: See https://www.asyncapi.com/docs/reference/specification/v3.0.0#schemaObject void shouldSerializeModelWithBooleans() throws JsonProcessingException { var schema = SchemaObject.builder() .type("object") - // .properties(Map.of( - // "anySchema", true, - // "cannotBeDefined", false)) + .properties(Map.of( + "anySchema", true, + "cannotBeDefined", false)) .required(List.of("anySchema")) .build(); 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 new file mode 100644 index 000000000..922074113 --- /dev/null +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-queue.yaml @@ -0,0 +1,13 @@ +channels: + userSignup: + address: 'user/signup' + bindings: + amqp: + is: queue + queue: + name: my-queue-name + durable: true + exclusive: true + autoDelete: false + vhost: / + bindingVersion: 0.3.0 \ No newline at end of file diff --git a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel.yaml b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml similarity index 65% rename from springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel.yaml rename to springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml index a4a197a4c..0bcaed1fb 100644 --- a/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel.yaml +++ b/springwolf-asyncapi/src/test/resources/v3/bindings/amqp/amqp-channel-routing.yaml @@ -4,12 +4,6 @@ channels: bindings: amqp: is: routingKey - queue: - name: my-queue-name - durable: true - exclusive: true - autoDelete: false - vhost: / exchange: name: myExchange type: topic diff --git a/springwolf-core/build.gradle b/springwolf-core/build.gradle index 78a6b6a98..bef06ddf1 100644 --- a/springwolf-core/build.gradle +++ b/springwolf-core/build.gradle @@ -8,7 +8,7 @@ plugins { } dependencies { - api "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" + api project(":springwolf-asyncapi") implementation "io.swagger.core.v3:swagger-annotations-jakarta:${swaggerVersion}@jar" implementation "io.swagger.core.v3:swagger-core-jakarta:${swaggerVersion}" @@ -44,8 +44,9 @@ dependencies { testCompileOnly "org.projectlombok:lombok:${lombokVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" - testImplementation("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}") + testImplementation "org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}" testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" + testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${jsonUnitAssertJVersion}" testImplementation "org.awaitility:awaitility:${awaitilityVersion}" testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" testImplementation "org.springframework.boot:spring-boot-test" diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfAutoConfiguration.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfAutoConfiguration.java index ef70e0f8e..0329fb4f6 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfAutoConfiguration.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfAutoConfiguration.java @@ -6,8 +6,11 @@ import io.github.stavshamir.springwolf.asyncapi.ChannelsService; import io.github.stavshamir.springwolf.asyncapi.DefaultAsyncApiService; import io.github.stavshamir.springwolf.asyncapi.DefaultChannelsService; +import io.github.stavshamir.springwolf.asyncapi.DefaultOperationsService; +import io.github.stavshamir.springwolf.asyncapi.OperationsService; import io.github.stavshamir.springwolf.asyncapi.SpringwolfInitApplicationListener; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.configuration.DefaultAsyncApiDocketService; @@ -58,9 +61,11 @@ public SpringwolfInitApplicationListener springwolfInitApplicationListener( public AsyncApiService asyncApiService( AsyncApiDocketService asyncApiDocketService, ChannelsService channelsService, + OperationsService operationsService, SchemasService schemasService, List customizers) { - return new DefaultAsyncApiService(asyncApiDocketService, channelsService, schemasService, customizers); + return new DefaultAsyncApiService( + asyncApiDocketService, channelsService, operationsService, schemasService, customizers); } @Bean @@ -69,6 +74,12 @@ public ChannelsService channelsService(List channelsS return new DefaultChannelsService(channelsScanners); } + @Bean + @ConditionalOnMissingBean + public OperationsService operationsService(List operationsScanners) { + return new DefaultOperationsService(operationsScanners); + } + @Bean @ConditionalOnMissingBean public SchemasService schemasService( diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfScannerConfiguration.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfScannerConfiguration.java index 58e3cbff4..cfdb03118 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfScannerConfiguration.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/SpringwolfScannerConfiguration.java @@ -7,6 +7,8 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncAnnotationOperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncAnnotationScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher; @@ -14,7 +16,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ConfigurationClassScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.SpringwolfClassScanner; -import io.github.stavshamir.springwolf.asyncapi.types.OperationData; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.schemas.SchemasService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -67,7 +69,7 @@ public SpringwolfClassScanner springwolfClassScanner( havingValue = "true", matchIfMissing = true) @Order(value = ChannelPriority.ASYNC_ANNOTATION) - public AsyncAnnotationChannelsScanner asyncListenerAnnotationScanner( + public AsyncAnnotationChannelsScanner asyncListenerAnnotationChannelScanner( SpringwolfClassScanner springwolfClassScanner, SchemasService schemasService, AsyncApiDocketService asyncApiDocketService, @@ -84,13 +86,34 @@ public AsyncAnnotationChannelsScanner asyncListenerAnnotationScan messageBindingProcessors); } + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_ASYNC_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.ASYNC_ANNOTATION) + public AsyncAnnotationOperationsScanner asyncListenerAnnotationOperationScanner( + SpringwolfClassScanner springwolfClassScanner, + SchemasService schemasService, + PayloadClassExtractor payloadClassExtractor, + List operationBindingProcessors, + List messageBindingProcessors) { + return new AsyncAnnotationOperationsScanner<>( + buildAsyncListenerAnnotationProvider(), + springwolfClassScanner, + schemasService, + payloadClassExtractor, + operationBindingProcessors, + messageBindingProcessors); + } + @Bean @ConditionalOnProperty( name = SPRINGWOLF_SCANNER_ASYNC_PUBLISHER_ENABLED, havingValue = "true", matchIfMissing = true) @Order(value = ChannelPriority.ASYNC_ANNOTATION) - public AsyncAnnotationChannelsScanner asyncPublisherAnnotationScanner( + public AsyncAnnotationChannelsScanner asyncPublisherChannelAnnotationScanner( SpringwolfClassScanner springwolfClassScanner, SchemasService schemasService, AsyncApiDocketService asyncApiDocketService, @@ -107,9 +130,30 @@ public AsyncAnnotationChannelsScanner asyncPublisherAnnotationSc messageBindingProcessors); } - private static AsyncAnnotationChannelsScanner.AsyncAnnotationProvider + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_ASYNC_PUBLISHER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.ASYNC_ANNOTATION) + public AsyncAnnotationOperationsScanner asyncPublisherOperationAnnotationScanner( + SpringwolfClassScanner springwolfClassScanner, + SchemasService schemasService, + PayloadClassExtractor payloadClassExtractor, + List operationBindingProcessors, + List messageBindingProcessors) { + return new AsyncAnnotationOperationsScanner<>( + buildAsyncPublisherAnnotationProvider(), + springwolfClassScanner, + schemasService, + payloadClassExtractor, + operationBindingProcessors, + messageBindingProcessors); + } + + private static AsyncAnnotationScanner.AsyncAnnotationProvider buildAsyncListenerAnnotationProvider() { - return new AsyncAnnotationChannelsScanner.AsyncAnnotationProvider<>() { + return new AsyncAnnotationScanner.AsyncAnnotationProvider<>() { @Override public Class getAnnotation() { return AsyncListener.class; @@ -121,15 +165,15 @@ public AsyncOperation getAsyncOperation(AsyncListener annotation) { } @Override - public OperationData.OperationType getOperationType() { - return OperationData.OperationType.PUBLISH; + public OperationAction getOperationType() { + return OperationAction.RECEIVE; } }; } - private static AsyncAnnotationChannelsScanner.AsyncAnnotationProvider + private static AsyncAnnotationScanner.AsyncAnnotationProvider buildAsyncPublisherAnnotationProvider() { - return new AsyncAnnotationChannelsScanner.AsyncAnnotationProvider<>() { + return new AsyncAnnotationScanner.AsyncAnnotationProvider<>() { @Override public Class getAnnotation() { return AsyncPublisher.class; @@ -141,8 +185,8 @@ public AsyncOperation getAsyncOperation(AsyncPublisher annotation) { } @Override - public OperationData.OperationType getOperationType() { - return OperationData.OperationType.SUBSCRIBE; + public OperationAction getOperationType() { + return OperationAction.SEND; } }; } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/ChannelsService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/ChannelsService.java index 3ce70c2c0..fe2ce0020 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/ChannelsService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/ChannelsService.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; import java.util.Map; @@ -11,9 +11,9 @@ public interface ChannelsService { /** - * Detects all available AsyncAPI ChannelItem in the spring context. + * Detects all available AsyncAPI ChannelObject in the spring context. * * @return Map of channel names mapping to detected ChannelItems */ - Map findChannels(); + Map findChannels(); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerService.java index e67e76d11..ff31d7498 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerService.java @@ -14,6 +14,7 @@ import io.swagger.v3.core.util.Yaml; import jakarta.annotation.PostConstruct; +// FIXME: Replace this class by the AsyncAPI 'DefaultAsyncApiSerializerService' public class DefaultAsyncApiSerializerService implements AsyncApiSerializerService { private ObjectMapper jsonMapper = Json.mapper(); diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiService.java index 5881202dd..dc9aa4206 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiService.java @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; import io.github.stavshamir.springwolf.asyncapi.types.Components; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -27,6 +28,7 @@ private record AsyncAPIResult(AsyncAPI asyncAPI, Throwable exception) {} private final AsyncApiDocketService asyncApiDocketService; private final ChannelsService channelsService; + private final OperationsService operationsService; private final SchemasService schemasService; private final List customizers; @@ -62,10 +64,13 @@ protected synchronized void initAsyncAPI() { // ChannelsService must be invoked before accessing SchemasService, // because during channel scanning, all detected schemas are registered with // SchemasService. - Map channels = channelsService.findChannels(); + Map channels = channelsService.findChannels(); + + Map operations = operationsService.findOperations(); Components components = Components.builder() - .schemas(schemasService.getDefinitions()) + .schemas(schemasService.getSchemas()) + .messages(schemasService.getMessages()) .build(); AsyncAPI asyncAPI = AsyncAPI.builder() @@ -74,6 +79,7 @@ protected synchronized void initAsyncAPI() { .defaultContentType(docket.getDefaultContentType()) .servers(docket.getServers()) .channels(channels) + .operations(operations) .components(components) .build(); diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsService.java index 7bce43aff..5b25ef5e5 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsService.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelMerger; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,22 +21,22 @@ public class DefaultChannelsService implements ChannelsService { private final List channelsScanners; /** - * Collects all AsyncAPI ChannelItems using the available {@link ChannelsScanner} + * Collects all AsyncAPI ChannelObjects using the available {@link ChannelsScanner} * beans. - * @return Map of channel names mapping to detected ChannelItems + * @return Map of channel names mapping to detected ChannelObject */ @Override - public Map findChannels() { - List> foundChannelItems = new ArrayList<>(); + public Map findChannels() { + List> foundChannelItems = new ArrayList<>(); for (ChannelsScanner scanner : channelsScanners) { try { - Map channels = scanner.scan(); + Map channels = scanner.scan(); foundChannelItems.addAll(channels.entrySet()); } catch (Exception e) { log.error("An error was encountered during channel scanning with {}: {}", scanner, e.getMessage(), e); } } - return ChannelMerger.merge(foundChannelItems); + return ChannelMerger.mergeChannels(foundChannelItems); } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsService.java new file mode 100644 index 000000000..22194e085 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsService.java @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi; + +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationMerger; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Service to detect AsyncAPI operations in the current spring context. + */ +@Slf4j +@RequiredArgsConstructor +public class DefaultOperationsService implements OperationsService { + + private final List operationScanners; + + /** + * Collects all AsyncAPI Operation using the available {@link OperationsScanner} + * beans. + * @return Map of operation names mapping to detected Operation + */ + @Override + public Map findOperations() { + List> foundOperations = new ArrayList<>(); + + for (OperationsScanner scanner : operationScanners) { + try { + Map channels = scanner.scan(); + foundOperations.addAll(channels.entrySet()); + } catch (Exception e) { + log.error("An error was encountered during operation scanning with {}: {}", scanner, e.getMessage(), e); + } + } + return OperationMerger.mergeOperations(foundOperations); + } +} 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 db7ee3615..7ab9c72f5 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,14 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; -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; @@ -17,35 +15,35 @@ @Slf4j public class MessageHelper { - private static final String ONE_OF = "oneOf"; + private static final Comparator byMessageName = Comparator.comparing(MessageObject::getName); - private static final Comparator byMessageName = Comparator.comparing(Message::getName); + private static final Supplier> messageSupplier = () -> new TreeSet<>(byMessageName); - private static final Supplier> messageSupplier = () -> new TreeSet<>(byMessageName); + private MessageHelper() {} - 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)))); - }; + 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::getName, MessageReference::toComponentMessage)); } - @SuppressWarnings("unchecked") - public static Set messageObjectToSet(Object messageObject) { - if (messageObject instanceof Message message) { - return new HashSet<>(Collections.singletonList(message)); + public static Map toOperationsMessagesMap( + String channelName, Set messages) { + if (channelName == null || channelName.isBlank()) { + throw new IllegalArgumentException("channelName must not be empty"); } - if (messageObject instanceof Map) { - List messages = ((Map>) messageObject).get(ONE_OF); - return new HashSet<>(messages); + if (messages.isEmpty()) { + throw new IllegalArgumentException("messages must not be empty"); } - log.warn( - "Message object must contain either a Message or a Map, but contained: {}", - messageObject.getClass()); - return new HashSet<>(); + return new ArrayList<>(messages.stream().collect(Collectors.toCollection(messageSupplier))) + .stream() + .collect(Collectors.toMap( + MessageObject::getName, + e -> MessageReference.toChannelMessage(channelName, e.getName()))); } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/OperationsService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/OperationsService.java new file mode 100644 index 000000000..059af34fb --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/OperationsService.java @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi; + +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; + +import java.util.Map; + +/** + * Service to detect AsyncAPI channels in the current spring context. + */ +public interface OperationsService { + + /** + * Detects all available AsyncAPI Operation in the spring context. + * + * @return Map of operation names mapping to detected Operations + */ + Map findOperations(); +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/PublishingPayloadCreator.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/PublishingPayloadCreator.java index 844c1ab4d..04bf39efd 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/PublishingPayloadCreator.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/controller/PublishingPayloadCreator.java @@ -27,7 +27,7 @@ public class PublishingPayloadCreator { public Result createPayloadObject(MessageDto message) { String messagePayloadType = message.getPayloadType(); - List knownSchemaNames = schemasService.getDefinitions().values().stream() + List knownSchemaNames = schemasService.getSchemas().values().stream() .map(Schema::getName) .toList(); for (String schemaPayloadType : knownSchemaNames) { diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingFactory.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingFactory.java index ccca8828b..281f7fb68 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingFactory.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingFactory.java @@ -1,18 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; +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 java.util.Map; public interface BindingFactory { String getChannelName(T annotation); - Map buildChannelBinding(T annotation); + Map buildChannelBinding(T annotation); - Map buildOperationBinding(T annotation); + Map buildOperationBinding(T annotation); - Map buildMessageBinding(T annotation); + Map buildMessageBinding(T annotation); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingProcessorPriority.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingProcessorPriority.java index 6c6057e76..59e67c260 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingProcessorPriority.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/BindingProcessorPriority.java @@ -29,4 +29,6 @@ public class BindingProcessorPriority { * Examples: Plugins like KafkaOperationBindingProcessor, etc */ public static final int PROTOCOL_BINDING = 3; + + private BindingProcessorPriority() {} } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedMessageBinding.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedMessageBinding.java index 4634e550d..7bf1c6b47 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedMessageBinding.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedMessageBinding.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.message.MessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; import lombok.Data; @Data diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedOperationBinding.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedOperationBinding.java index 3ba4e9557..2e08d226a 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedOperationBinding.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/ProcessedOperationBinding.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.operation.OperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; import lombok.Data; @Data diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMerger.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMerger.java index 498351806..b82da5f66 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMerger.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelMerger.java @@ -1,80 +1,63 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import io.github.stavshamir.springwolf.asyncapi.MessageHelper; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.Channel; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessageObjectOrComposition; /** - * Util to merge multiple {@link ChannelItem}s + * Util to merge multiple {@link Channel}s */ public class ChannelMerger { + private ChannelMerger() {} + /** - * Merges multiple channelItems by channel name + * Merges multiple channels by channel name *

- * Given two channelItems for the same channel name, the first seen ChannelItem is used - * If an operation is null, the next non-null operation is used - * Messages within operations are merged + * Given two channels for the same channel name, the first seen Channel is used + * Messages within channels are merged * - * @param channelEntries Ordered pairs of channel name to ChannelItem - * @return A map of channelName to a single ChannelItem + * @param channelEntries Ordered pairs of channel name to Channel + * @return A map of channelName to a single Channel */ - public static Map merge(List> channelEntries) { - Map mergedChannels = new HashMap<>(); + public static Map mergeChannels(List> channelEntries) { + Map mergedChannels = new HashMap<>(); - for (Map.Entry entry : channelEntries) { + for (Map.Entry entry : channelEntries) { if (!mergedChannels.containsKey(entry.getKey())) { mergedChannels.put(entry.getKey(), entry.getValue()); } else { - ChannelItem channelItem = mergedChannels.get(entry.getKey()); - channelItem.setPublish(mergeOperation( - channelItem.getPublish(), entry.getValue().getPublish())); - channelItem.setSubscribe(mergeOperation( - channelItem.getSubscribe(), entry.getValue().getSubscribe())); + ChannelObject channel = mergeChannel(mergedChannels.get(entry.getKey()), entry.getValue()); + mergedChannels.put(entry.getKey(), channel); } } return mergedChannels; } - private static Operation mergeOperation(Operation operation, Operation otherOperation) { - Operation mergedOperation = operation != null ? operation : otherOperation; - - Set mergedMessages = mergeMessages(getMessages(operation), getMessages(otherOperation)); - if (!mergedMessages.isEmpty()) { - mergedOperation.setMessage(toMessageObjectOrComposition(mergedMessages)); - } - return mergedOperation; - } + private static ChannelObject mergeChannel(ChannelObject channel, ChannelObject otherChannel) { + ChannelObject mergedChannel = channel != null ? channel : otherChannel; - private static Set mergeMessages(Set messages, Set otherMessages) { - Map nameToMessage = - messages.stream().collect(Collectors.toMap(Message::getName, Function.identity())); + Map channelMessages = channel.getMessages(); + Map otherChannelMessages = otherChannel.getMessages(); - for (Message otherMessage : otherMessages) { - nameToMessage.putIfAbsent(otherMessage.getName(), otherMessage); + Map mergedMessages = new HashMap<>(); + if (channelMessages != null) { + mergedMessages.putAll(channelMessages); + } + if (otherChannelMessages != null) { + otherChannelMessages.forEach(mergedMessages::putIfAbsent); } - return new HashSet<>(nameToMessage.values()); - } + if (!mergedMessages.isEmpty()) { + mergedChannel.setMessages(mergedMessages); + } - private static Set getMessages(Operation operation) { - return Optional.ofNullable(operation) - .map(Operation::getMessage) - .map(MessageHelper::messageObjectToSet) - .orElseGet(HashSet::new); + return mergedChannel; } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelPriority.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelPriority.java index 7ffacfa1c..e09e606dd 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelPriority.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelPriority.java @@ -26,4 +26,6 @@ public class ChannelPriority { * Examples: Plugins like KafkaListener, etc */ public static final int AUTO_DISCOVERED = 3; + + private ChannelPriority() {} } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelsScanner.java index 9031b6ba6..6528eb439 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/ChannelsScanner.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; import java.util.Map; @@ -10,5 +10,5 @@ public interface ChannelsScanner { /** * @return A mapping of channel names to their respective channel object for a given protocol. */ - Map scan(); + Map scan(); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMerger.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMerger.java new file mode 100644 index 000000000..e3e70b20e --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMerger.java @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels; + +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.Channel; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Util to merge multiple {@link Channel}s + */ +public class OperationMerger { + + private OperationMerger() {} + + /** + * Merges multiple operations by operation name + *

+ * Given two operations for the same operation name, the first seen Operation is used + * If an operation is null, the next non-null operation is used + * Messages within operations are merged + * + * @param operationEntries Ordered pairs of operation name to Operation + * @return A map of operationId to a single Operation + */ + public static Map mergeOperations(List> operationEntries) { + Map mergedOperations = new HashMap<>(); + + for (Map.Entry entry : operationEntries) { + if (!mergedOperations.containsKey(entry.getKey())) { + mergedOperations.put(entry.getKey(), entry.getValue()); + } else { + Operation operation = mergeOperation(mergedOperations.get(entry.getKey()), entry.getValue()); + mergedOperations.put(entry.getKey(), operation); + } + } + + return mergedOperations; + } + + private static Operation mergeOperation(Operation operation, Operation otherOperation) { + Operation mergedOperation = operation != null ? operation : otherOperation; + + List mergedMessages = + mergeMessageReferences(operation.getMessages(), otherOperation.getMessages()); + if (!mergedMessages.isEmpty()) { + mergedOperation.setMessages(mergedMessages); + } + return mergedOperation; + } + + private static List mergeMessageReferences( + Collection messages, Collection otherMessages) { + var messageReferences = new HashSet(); + if (messages != null) { + messageReferences.addAll(messages); + } + if (otherMessages != null) { + messageReferences.addAll(otherMessages); + } + return messageReferences.stream().toList(); + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationsScanner.java new file mode 100644 index 000000000..0e8b1c9e2 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationsScanner.java @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels; + +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; + +import java.util.Map; + +public interface OperationsScanner { + + /** + * @return A mapping of operation names to their respective operation object for a given protocol. + */ + Map scan(); +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScanner.java index 0bf5b3193..b5aa8bfdd 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScanner.java @@ -1,14 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; import lombok.RequiredArgsConstructor; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; @RequiredArgsConstructor @@ -19,19 +18,19 @@ public class SimpleChannelsScanner implements ChannelsScanner { private final ClassProcessor classProcessor; @Override - public Map scan() { + public Map scan() { Set> components = classScanner.scan(); - List> channels = mapToChannels(components); + List> channels = mapToChannels(components); - return ChannelMerger.merge(channels); + return ChannelMerger.mergeChannels(channels); } - private List> mapToChannels(Set> components) { - return components.stream().flatMap(classProcessor::process).collect(Collectors.toList()); + private List> mapToChannels(Set> components) { + return components.stream().flatMap(classProcessor::process).toList(); } public interface ClassProcessor { - Stream> process(Class clazz); + Stream> process(Class clazz); } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScanner.java new file mode 100644 index 000000000..b8b986df2 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScanner.java @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels; + +import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class SimpleOperationsScanner implements OperationsScanner { + + private final ClassScanner classScanner; + + private final ClassProcessor classProcessor; + + @Override + public Map scan() { + Set> components = classScanner.scan(); + + List> operations = mapToOperations(components); + + return OperationMerger.mergeOperations(operations); + } + + private List> mapToOperations(Set> components) { + return components.stream().flatMap(classProcessor::process).toList(); + } + + public interface ClassProcessor { + Stream> process(Class clazz); + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtil.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtil.java index e336b0f93..03b5b8713 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtil.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtil.java @@ -14,6 +14,8 @@ public class AnnotationUtil { + private AnnotationUtil() {} + public static T findAnnotationOrThrow(Class annotationClass, AnnotatedElement element) { T annotation = findAnnotation(annotationClass, element); if (annotation == null) { diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScanner.java index 805af5050..79aa4b518 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScanner.java @@ -1,11 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.server.Server; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelMerger; @@ -13,163 +8,77 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; -import io.github.stavshamir.springwolf.asyncapi.types.OperationData; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ServerReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.schemas.SchemasService; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.EmbeddedValueResolverAware; -import org.springframework.util.StringUtils; -import org.springframework.util.StringValueResolver; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.toList; @Slf4j -@RequiredArgsConstructor -public class AsyncAnnotationChannelsScanner - implements ChannelsScanner, EmbeddedValueResolverAware { +public class AsyncAnnotationChannelsScanner extends AsyncAnnotationScanner + implements ChannelsScanner { - private final AsyncAnnotationProvider asyncAnnotationProvider; private final ClassScanner classScanner; - private final SchemasService schemasService; private final AsyncApiDocketService asyncApiDocketService; - private final PayloadClassExtractor payloadClassExtractor; - private final List operationBindingProcessors; - private final List messageBindingProcessors; - private StringValueResolver resolver; - @Override - public void setEmbeddedValueResolver(StringValueResolver resolver) { - this.resolver = resolver; + public AsyncAnnotationChannelsScanner( + AsyncAnnotationProvider asyncAnnotationProvider, + ClassScanner classScanner, + SchemasService schemasService, + AsyncApiDocketService asyncApiDocketService, + PayloadClassExtractor payloadClassExtractor, + List operationBindingProcessors, + List messageBindingProcessors) { + super( + asyncAnnotationProvider, + payloadClassExtractor, + schemasService, + operationBindingProcessors, + messageBindingProcessors); + this.classScanner = classScanner; + this.asyncApiDocketService = asyncApiDocketService; } @Override - public Map scan() { - List> channels = classScanner.scan().stream() + public Map scan() { + List> channels = classScanner.scan().stream() .flatMap(this::getAnnotatedMethods) - .map(this::buildChannelItem) - .filter(this::isInvalidChannelItem) - .collect(toList()); - - return ChannelMerger.merge(channels); - } - - private Stream> getAnnotatedMethods(Class type) { - Class annotationClass = this.asyncAnnotationProvider.getAnnotation(); - log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName()); + .map(this::buildChannel) + .toList(); - return Arrays.stream(type.getDeclaredMethods()) - .filter(method -> !method.isBridge()) - .filter(method -> AnnotationUtil.findAnnotation(annotationClass, method) != null) - .peek(method -> log.debug("Mapping method \"{}\" to channels", method.getName())) - .flatMap(method -> AnnotationUtil.findAnnotations(annotationClass, method).stream() - .map(annotation -> new MethodAndAnnotation<>(method, annotation))); + return ChannelMerger.mergeChannels(channels); } - private boolean isInvalidChannelItem(Map.Entry entry) { - Operation publish = entry.getValue().getPublish(); - boolean publishBindingExists = publish != null && publish.getBindings() != null; - - Operation subscribe = entry.getValue().getSubscribe(); - boolean subscribeBindingExists = subscribe != null && subscribe.getBindings() != null; - - boolean allNonNull = entry.getKey() != null && (publishBindingExists || subscribeBindingExists); - - if (!allNonNull) { - log.warn( - "Some data fields are null - method (channel={}) will not be documented: {}", - entry.getKey(), - entry.getValue()); - } - - return allNonNull; - } - - private Map.Entry buildChannelItem(MethodAndAnnotation methodAndAnnotation) { - ChannelItem.ChannelItemBuilder channelBuilder = ChannelItem.builder(); + private Map.Entry buildChannel(MethodAndAnnotation methodAndAnnotation) { + ChannelObject.ChannelObjectBuilder channelBuilder = ChannelObject.builder(); AsyncOperation operationAnnotation = this.asyncAnnotationProvider.getAsyncOperation(methodAndAnnotation.annotation()); String channelName = resolver.resolveStringValue(operationAnnotation.channelName()); Operation operation = buildOperation(operationAnnotation, methodAndAnnotation.method(), channelName); - switch (this.asyncAnnotationProvider.getOperationType()) { - case PUBLISH -> channelBuilder.publish(operation); - case SUBSCRIBE -> channelBuilder.subscribe(operation); - } - ; List servers = AsyncAnnotationScannerUtil.getServers(operationAnnotation, resolver); if (servers != null && !servers.isEmpty()) { - validateServers(servers, operation.getOperationId()); - channelBuilder.servers(servers); - } - - ChannelItem channelItem = channelBuilder.build(); - return Map.entry(channelName, channelItem); - } - - private Operation buildOperation(AsyncOperation asyncOperation, Method method, String channelName) { - String description = this.resolver.resolveStringValue(asyncOperation.description()); - if (!StringUtils.hasText(description)) { - description = "Auto-generated description"; + validateServers(servers, operation.getTitle()); + channelBuilder.servers(servers.stream() + .map(it -> ServerReference.builder().ref(it).build()) + .toList()); } + MessageObject message = buildMessage(operationAnnotation, methodAndAnnotation.method()); - String operationId = channelName + "_" + this.asyncAnnotationProvider.getOperationType().operationName; - - Map operationBinding = - AsyncAnnotationScannerUtil.processOperationBindingFromAnnotation(method, operationBindingProcessors); - Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - - return Operation.builder() - .description(description) - .operationId(operationId) - .message(buildMessage(asyncOperation, method)) - .bindings(opBinding) + ChannelObject channelItem = channelBuilder + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) .build(); - } - - private Message buildMessage(AsyncOperation operationData, Method method) { - Class payloadType = operationData.payloadType() != Object.class - ? operationData.payloadType() - : payloadClassExtractor.extractFrom(method); - - String modelName = this.schemasService.register(payloadType); - AsyncHeaders asyncHeaders = AsyncAnnotationScannerUtil.getAsyncHeaders(operationData, resolver); - String headerModelName = this.schemasService.register(asyncHeaders); - - var schema = payloadType.getAnnotation(Schema.class); - String description = schema != null ? schema.description() : null; - - Map messageBinding = - AsyncAnnotationScannerUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors); - - var builder = Message.builder() - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) - .description(description) - .payload(PayloadReference.fromModelName(modelName)) - .headers(HeaderReference.fromModelName(headerModelName)) - .bindings(messageBinding); - - // Retrieve the Message information obtained from the @AsyncMessage annotation. These values have higher - // priority so if we find them, we need to override the default values. - AsyncAnnotationScannerUtil.processAsyncMessageAnnotation(builder, operationData.message(), this.resolver); - - return builder.build(); + return Map.entry(channelName, channelItem); } /** @@ -198,14 +107,4 @@ void validateServers(List serversFromOperation, String operationId) { } } } - - public interface AsyncAnnotationProvider { - Class getAnnotation(); - - AsyncOperation getAsyncOperation(A annotation); - - OperationData.OperationType getOperationType(); - } - - private record MethodAndAnnotation(Method method, A annotation) {} } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScanner.java new file mode 100644 index 000000000..5f73dfa76 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScanner.java @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationMerger; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import lombok.extern.slf4j.Slf4j; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; + +@Slf4j +public class AsyncAnnotationOperationsScanner extends AsyncAnnotationScanner + implements OperationsScanner { + + private final ClassScanner classScanner; + + public AsyncAnnotationOperationsScanner( + AsyncAnnotationProvider asyncAnnotationProvider, + ClassScanner classScanner, + SchemasService schemasService, + PayloadClassExtractor payloadClassExtractor, + List operationBindingProcessors, + List messageBindingProcessors) { + super( + asyncAnnotationProvider, + payloadClassExtractor, + schemasService, + operationBindingProcessors, + messageBindingProcessors); + this.classScanner = classScanner; + } + + @Override + public Map scan() { + List> operations = classScanner.scan().stream() + .flatMap(this::getAnnotatedMethods) + .map(this::buildOperation) + .toList(); + + return OperationMerger.mergeOperations(operations); + } + + private Map.Entry buildOperation(MethodAndAnnotation methodAndAnnotation) { + AsyncOperation operationAnnotation = + this.asyncAnnotationProvider.getAsyncOperation(methodAndAnnotation.annotation()); + String channelName = resolver.resolveStringValue(operationAnnotation.channelName()); + String operationId = channelName + "_" + this.asyncAnnotationProvider.getOperationType().type + "_" + + methodAndAnnotation.method().getName(); + + Operation operation = buildOperation(operationAnnotation, methodAndAnnotation.method(), channelName); + operation.setAction(this.asyncAnnotationProvider.getOperationType()); + + return Map.entry(operationId, operation); + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScanner.java new file mode 100644 index 000000000..a581cee71 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScanner.java @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.ChannelReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +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 io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@Slf4j +@RequiredArgsConstructor +public abstract class AsyncAnnotationScanner implements EmbeddedValueResolverAware { + + protected final AsyncAnnotationProvider asyncAnnotationProvider; + protected final PayloadClassExtractor payloadClassExtractor; + protected final SchemasService schemasService; + protected final List operationBindingProcessors; + protected final List messageBindingProcessors; + protected StringValueResolver resolver; + + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.resolver = resolver; + } + + protected Stream> getAnnotatedMethods(Class type) { + Class annotationClass = this.asyncAnnotationProvider.getAnnotation(); + log.debug("Scanning class \"{}\" for @\"{}\" annotated methods", type.getName(), annotationClass.getName()); + + return Arrays.stream(type.getDeclaredMethods()) + .filter(method -> !method.isBridge()) + .filter(method -> AnnotationUtil.findAnnotation(annotationClass, method) != null) + .peek(method -> log.debug("Mapping method \"{}\" to channels", method.getName())) + .flatMap(method -> AnnotationUtil.findAnnotations(annotationClass, method).stream() + .map(annotation -> new MethodAndAnnotation<>(method, annotation))); + } + + protected Operation buildOperation(AsyncOperation asyncOperation, Method method, String channelName) { + String description = this.resolver.resolveStringValue(asyncOperation.description()); + if (!StringUtils.hasText(description)) { + description = "Auto-generated description"; + } + + String operationTitle = channelName + "_" + this.asyncAnnotationProvider.getOperationType().type; + + Map operationBinding = + AsyncAnnotationScannerUtil.processOperationBindingFromAnnotation(method, operationBindingProcessors); + Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; + MessageObject message = buildMessage(asyncOperation, method); + + return Operation.builder() + .channel(ChannelReference.fromChannel(channelName)) + .description(description) + .title(operationTitle) + .messages(List.of(MessageReference.toChannelMessage(channelName, message))) + .bindings(opBinding) + .build(); + } + + protected MessageObject buildMessage(AsyncOperation operationData, Method method) { + Class payloadType = operationData.payloadType() != Object.class + ? operationData.payloadType() + : payloadClassExtractor.extractFrom(method); + + String modelName = this.schemasService.registerSchema(payloadType); + AsyncHeaders asyncHeaders = AsyncAnnotationScannerUtil.getAsyncHeaders(operationData, resolver); + String headerModelName = this.schemasService.registerSchema(asyncHeaders); + var headers = MessageHeaders.of(MessageReference.toSchema(headerModelName)); + + var schema = payloadType.getAnnotation(Schema.class); + String description = schema != null ? schema.description() : null; + + Map messageBinding = + AsyncAnnotationScannerUtil.processMessageBindingFromAnnotation(method, messageBindingProcessors); + + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(modelName)) + .build()); + + var builder = MessageObject.builder() + .messageId(payloadType.getName()) + .name(payloadType.getName()) + .title(payloadType.getSimpleName()) + .description(description) + .payload(messagePayload) + .headers(headers) + .bindings(messageBinding); + + // Retrieve the Message information obtained from the @AsyncMessage annotation. These values have higher + // priority so if we find them, we need to override the default values. + AsyncAnnotationScannerUtil.processAsyncMessageAnnotation(builder, operationData.message(), this.resolver); + + MessageObject message = builder.build(); + this.schemasService.registerMessage(message); + return message; + } + + public interface AsyncAnnotationProvider { + Class getAnnotation(); + + AsyncOperation getAsyncOperation(A annotation); + + OperationAction getOperationType(); + } + + protected record MethodAndAnnotation(Method method, A annotation) {} +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtil.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtil.java index 9c0100f37..dfc9d3cb3 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtil.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtil.java @@ -1,17 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncMessage; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaderSchema; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.message.MessageObject; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; @@ -25,6 +25,8 @@ import static java.util.stream.Collectors.groupingBy; class AsyncAnnotationScannerUtil { + private AsyncAnnotationScannerUtil() {} + public static AsyncHeaders getAsyncHeaders(AsyncOperation op, StringValueResolver resolver) { if (op.headers().values().length == 0) { return AsyncHeaders.NOT_DOCUMENTED; @@ -42,6 +44,14 @@ public static AsyncHeaders getAsyncHeaders(AsyncOperation op, StringValueResolve .enumValue(values) .example(exampleValue) .build()); + + // FIXME: Replace AsyncHeaders by proper AsyncAPI v3 Headers + // MessageHeaders.of( + // SchemaObject.builder() + // .description(getDescription(headers, resolver)) + // .enumValues(values) + // .examples(exampleValue != null ? List.of(exampleValue) : null) + // .build()); }); return asyncHeaders; @@ -53,7 +63,7 @@ private static List getHeaderValues( .map(AsyncOperation.Headers.Header::value) .map(resolver::resolveStringValue) .sorted() - .collect(Collectors.toList()); + .toList(); } private static String getDescription(List value, StringValueResolver resolver) { @@ -83,11 +93,13 @@ public static Map processMessageBindingFromAnnotation( .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toMap( - ProcessedMessageBinding::getType, ProcessedMessageBinding::getBinding, (e1, e2) -> e2)); + ProcessedMessageBinding::getType, ProcessedMessageBinding::getBinding, (e1, e2) -> e1)); } public static void processAsyncMessageAnnotation( - Message.MessageBuilder messageBuilder, AsyncMessage asyncMessage, StringValueResolver resolver) { + MessageObject.MessageObjectBuilder messageBuilder, + AsyncMessage asyncMessage, + StringValueResolver resolver) { String annotationMessageDescription = resolver.resolveStringValue(asyncMessage.description()); if (StringUtils.hasText(annotationMessageDescription)) { messageBuilder.description(annotationMessageDescription); @@ -103,10 +115,6 @@ public static void processAsyncMessageAnnotation( messageBuilder.name(annotationName); } - String annotationSchemaFormat = asyncMessage.schemaFormat(); - var schemaFormat = annotationSchemaFormat != null ? annotationSchemaFormat : Message.DEFAULT_SCHEMA_FORMAT; - messageBuilder.schemaFormat(schemaFormat); - String annotationTitle = resolver.resolveStringValue(asyncMessage.title()); if (StringUtils.hasText(annotationTitle)) { messageBuilder.title(annotationTitle); @@ -114,7 +122,7 @@ public static void processAsyncMessageAnnotation( } /** - * extracts servers array from the given AsyncOperation, resolves placeholdes with spring variables and + * extracts servers array from the given AsyncOperation, resolves placeholders with spring variables and * return a List of server names. * * @param op the given AsyncOperation @@ -122,6 +130,6 @@ public static void processAsyncMessageAnnotation( * @return List of server names */ public static List getServers(AsyncOperation op, StringValueResolver resolver) { - return Arrays.stream(op.servers()).map(resolver::resolveStringValue).collect(Collectors.toList()); + return Arrays.stream(op.servers()).map(resolver::resolveStringValue).toList(); } } 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 77c103811..6ac10900d 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 @@ -1,72 +1,54 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersBuilder; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.stavshamir.springwolf.schemas.SchemasService; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.annotation.AnnotationUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessageObjectOrComposition; -import static java.util.stream.Collectors.toSet; - -@RequiredArgsConstructor @Slf4j public class ClassLevelAnnotationChannelsScanner< ClassAnnotation extends Annotation, MethodAnnotation extends Annotation> + extends ClassLevelAnnotationScanner implements SimpleChannelsScanner.ClassProcessor { - private final Class classAnnotationClass; - private final Class methodAnnotationClass; - private final BindingFactory bindingFactory; - private final AsyncHeadersBuilder asyncHeadersBuilder; - private final PayloadClassExtractor payloadClassExtractor; - private final SchemasService schemasService; + public ClassLevelAnnotationChannelsScanner( + Class classAnnotationClass, + Class methodAnnotationClass, + BindingFactory bindingFactory, + AsyncHeadersBuilder asyncHeadersBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + super( + classAnnotationClass, + methodAnnotationClass, + bindingFactory, + asyncHeadersBuilder, + payloadClassExtractor, + schemasService); + } @Override - public Stream> process(Class clazz) { + public Stream> process(Class clazz) { log.debug( "Scanning class \"{}\" for @\"{}\" annotated methods", clazz.getName(), classAnnotationClass.getName()); return Stream.of(clazz).filter(this::isClassAnnotated).flatMap(this::mapClassToChannel); } - private boolean isClassAnnotated(Class component) { - return AnnotationUtil.findAnnotation(classAnnotationClass, component) != null; - } - - private Set getAnnotatedMethods(Class clazz) { - log.debug( - "Scanning class \"{}\" for @\"{}\" annotated methods", - clazz.getName(), - methodAnnotationClass.getName()); - - return Arrays.stream(clazz.getDeclaredMethods()) - .filter(method -> !method.isBridge()) - .filter(method -> AnnotationUtils.findAnnotation(method, methodAnnotationClass) != null) - .collect(toSet()); - } - - private Stream> mapClassToChannel(Class component) { + private Stream> mapClassToChannel(Class component) { log.debug("Mapping class \"{}\" to channels", component.getName()); ClassAnnotation classAnnotation = AnnotationUtil.findAnnotationOrThrow(classAnnotationClass, component); @@ -77,61 +59,23 @@ private Stream> mapClassToChannel(Class compon } String channelName = bindingFactory.getChannelName(classAnnotation); - String operationId = channelName + "_publish_" + component.getSimpleName(); - ChannelItem channelItem = buildChannelItem(classAnnotation, operationId, annotatedMethods); + ChannelObject channelItem = buildChannelItem(classAnnotation, annotatedMethods); return Stream.of(Map.entry(channelName, channelItem)); } - private ChannelItem buildChannelItem(ClassAnnotation classAnnotation, String operationId, Set methods) { - Object message = buildMessageObject(classAnnotation, methods); - Operation operation = buildOperation(classAnnotation, operationId, message); - return buildChannelItem(classAnnotation, operation); - } - - private Object buildMessageObject(ClassAnnotation classAnnotation, Set methods) { - Set messages = methods.stream() - .map((Method method) -> { - Class payloadType = payloadClassExtractor.extractFrom(method); - return buildMessage(classAnnotation, payloadType); - }) - .collect(toSet()); - - return toMessageObjectOrComposition(messages); + private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Set methods) { + var messages = buildMessages(classAnnotation, methods, MessageType.CHANNEL); + return buildChannelItem(classAnnotation, messages); } - private Message buildMessage(ClassAnnotation classAnnotation, Class payloadType) { - Map messageBinding = bindingFactory.buildMessageBinding(classAnnotation); - String modelName = schemasService.register(payloadType); - String headerModelName = schemasService.register(asyncHeadersBuilder.buildHeaders(payloadType)); - - return Message.builder() - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) - .description(null) - .payload(PayloadReference.fromModelName(modelName)) - .headers(HeaderReference.fromModelName(headerModelName)) - .bindings(messageBinding) + 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) + .messages(new HashMap<>(messages)) .build(); } - - private Operation buildOperation(ClassAnnotation classAnnotation, String operationId, Object message) { - Map operationBinding = - bindingFactory.buildOperationBinding(classAnnotation); - Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - - return Operation.builder() - .description("Auto-generated description") - .operationId(operationId) - .message(message) - .bindings(opBinding) - .build(); - } - - private ChannelItem buildChannelItem(ClassAnnotation classAnnotation, Operation operation) { - Map channelBinding = bindingFactory.buildChannelBinding(classAnnotation); - Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; - return ChannelItem.builder().bindings(chBinding).publish(operation).build(); - } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScanner.java new file mode 100644 index 000000000..31700cbb6 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScanner.java @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersBuilder; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import lombok.extern.slf4j.Slf4j; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +@Slf4j +public class ClassLevelAnnotationOperationsScanner< + ClassAnnotation extends Annotation, MethodAnnotation extends Annotation> + extends ClassLevelAnnotationScanner + implements SimpleOperationsScanner.ClassProcessor { + + public ClassLevelAnnotationOperationsScanner( + Class classAnnotationClass, + Class methodAnnotationClass, + BindingFactory bindingFactory, + AsyncHeadersBuilder asyncHeadersBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + super( + classAnnotationClass, + methodAnnotationClass, + bindingFactory, + asyncHeadersBuilder, + payloadClassExtractor, + schemasService); + } + + @Override + public Stream> process(Class clazz) { + log.debug( + "Scanning class \"{}\" for @\"{}\" annotated methods", clazz.getName(), classAnnotationClass.getName()); + + return Stream.of(clazz).filter(this::isClassAnnotated).flatMap(this::mapClassToOperation); + } + + private Stream> mapClassToOperation(Class component) { + log.debug("Mapping class \"{}\" to operations", component.getName()); + + ClassAnnotation classAnnotation = AnnotationUtil.findAnnotationOrThrow(classAnnotationClass, component); + + Set annotatedMethods = getAnnotatedMethods(component); + if (annotatedMethods.isEmpty()) { + return Stream.empty(); + } + + String channelName = bindingFactory.getChannelName(classAnnotation); + String operationId = channelName + "_" + OperationAction.RECEIVE + "_" + component.getSimpleName(); + + Operation operation = buildOperation(classAnnotation, annotatedMethods); + + return Stream.of(Map.entry(operationId, operation)); + } + + private Operation buildOperation(ClassAnnotation classAnnotation, Set methods) { + var messages = buildMessages(classAnnotation, methods, MessageType.OPERATION); + return buildOperation(classAnnotation, messages); + } + + 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); + + return Operation.builder() + .action(OperationAction.RECEIVE) + .channel(ChannelReference.fromChannel(channelName)) + .messages(messages.values().stream().toList()) + .bindings(opBinding) + .build(); + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationScanner.java new file mode 100644 index 000000000..9c6147b8e --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationScanner.java @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersBuilder; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; +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.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; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessagesMap; +import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toOperationsMessagesMap; +import static java.util.stream.Collectors.toSet; + +@Slf4j +@RequiredArgsConstructor +public abstract class ClassLevelAnnotationScanner< + ClassAnnotation extends Annotation, MethodAnnotation extends Annotation> { + + protected final Class classAnnotationClass; + protected final Class methodAnnotationClass; + protected final BindingFactory bindingFactory; + protected final AsyncHeadersBuilder asyncHeadersBuilder; + protected final PayloadClassExtractor payloadClassExtractor; + protected final SchemasService schemasService; + + protected enum MessageType { + CHANNEL, + OPERATION; + } + + protected boolean isClassAnnotated(Class component) { + return AnnotationUtil.findAnnotation(classAnnotationClass, component) != null; + } + + protected Set getAnnotatedMethods(Class clazz) { + log.debug( + "Scanning class \"{}\" for @\"{}\" annotated methods", + clazz.getName(), + methodAnnotationClass.getName()); + + return Arrays.stream(clazz.getDeclaredMethods()) + .filter(method -> !method.isBridge()) + .filter(method -> AnnotationUtils.findAnnotation(method, methodAnnotationClass) != null) + .collect(toSet()); + } + + protected Map buildMessages( + ClassAnnotation classAnnotation, + Set methods, + ClassLevelAnnotationOperationsScanner.MessageType messageType) { + Set messages = methods.stream() + .map((Method method) -> { + Class payloadType = payloadClassExtractor.extractFrom(method); + return buildMessage(classAnnotation, payloadType); + }) + .collect(toSet()); + + if (messageType == MessageType.OPERATION) { + String channelName = bindingFactory.getChannelName(classAnnotation); + return toOperationsMessagesMap(channelName, messages); + } + return toMessagesMap(messages); + } + + protected MessageObject buildMessage(ClassAnnotation classAnnotation, Class payloadType) { + Map messageBinding = bindingFactory.buildMessageBinding(classAnnotation); + String modelName = schemasService.registerSchema(payloadType); + String headerModelName = schemasService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType)); + + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(modelName)) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(payloadType.getName()) + .name(payloadType.getName()) + .title(payloadType.getSimpleName()) + .description(null) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) + .bindings(messageBinding) + .build(); + + this.schemasService.registerMessage(message); + return message; + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScanner.java index f52d02290..7605d91f7 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScanner.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScanner.java @@ -1,20 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.stavshamir.springwolf.schemas.SchemasService; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.lang.annotation.Annotation; @@ -24,18 +18,25 @@ import java.util.Map; import java.util.stream.Stream; -@RequiredArgsConstructor @Slf4j public class MethodLevelAnnotationChannelsScanner - implements SimpleChannelsScanner.ClassProcessor { + extends MethodLevelAnnotationScanner implements SimpleChannelsScanner.ClassProcessor { private final Class methodAnnotationClass; - private final BindingFactory bindingFactory; private final PayloadClassExtractor payloadClassExtractor; - private final SchemasService schemasService; + + public MethodLevelAnnotationChannelsScanner( + Class methodAnnotationClass, + BindingFactory bindingFactory, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + super(bindingFactory, schemasService); + this.methodAnnotationClass = methodAnnotationClass; + this.payloadClassExtractor = payloadClassExtractor; + } @Override - public Stream> process(Class clazz) { + public Stream> process(Class clazz) { log.debug( "Scanning class \"{}\" for @\"{}\" annotated methods", clazz.getName(), @@ -47,56 +48,30 @@ public Stream> process(Class clazz) { .map(this::mapMethodToChannel); } - private Map.Entry mapMethodToChannel(Method method) { + private Map.Entry mapMethodToChannel(Method method) { log.debug("Mapping method \"{}\" to channels", method.getName()); MethodAnnotation annotation = AnnotationUtil.findAnnotationOrThrow(methodAnnotationClass, method); String channelName = bindingFactory.getChannelName(annotation); - String operationId = channelName + "_publish_" + method.getName(); Class payload = payloadClassExtractor.extractFrom(method); - ChannelItem channelItem = buildChannelItem(annotation, operationId, payload); + ChannelObject channelItem = buildChannelItem(annotation, payload); return Map.entry(channelName, channelItem); } - private ChannelItem buildChannelItem(MethodAnnotation annotation, String operationId, Class payloadType) { - Message message = buildMessage(annotation, payloadType); - Operation operation = buildOperation(annotation, operationId, message); - return buildChannelItem(annotation, operation); + private ChannelObject buildChannelItem(MethodAnnotation annotation, Class payloadType) { + MessageObject message = buildMessage(annotation, payloadType); + return buildChannelItem(annotation, message); } - private Message buildMessage(MethodAnnotation annotation, Class payloadType) { - Map messageBinding = bindingFactory.buildMessageBinding(annotation); - String modelName = schemasService.register(payloadType); - String headerModelName = schemasService.register(AsyncHeaders.NOT_DOCUMENTED); - - return Message.builder() - .name(payloadType.getName()) - .title(payloadType.getSimpleName()) - .description(null) - .payload(PayloadReference.fromModelName(modelName)) - .headers(HeaderReference.fromModelName(headerModelName)) - .bindings(messageBinding) + private ChannelObject buildChannelItem(MethodAnnotation annotation, MessageObject message) { + Map channelBinding = bindingFactory.buildChannelBinding(annotation); + Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; + return ChannelObject.builder() + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) + .bindings(chBinding) .build(); } - - private Operation buildOperation(MethodAnnotation annotation, String operationId, Message message) { - Map operationBinding = bindingFactory.buildOperationBinding(annotation); - Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; - - return Operation.builder() - .description("Auto-generated description") - .operationId(operationId) - .message(message) - .bindings(opBinding) - .build(); - } - - private ChannelItem buildChannelItem(MethodAnnotation annotation, Operation operation) { - Map channelBinding = bindingFactory.buildChannelBinding(annotation); - Map chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null; - return ChannelItem.builder().bindings(chBinding).publish(operation).build(); - } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationOperationsScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationOperationsScanner.java new file mode 100644 index 000000000..18ccd4337 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationOperationsScanner.java @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import lombok.extern.slf4j.Slf4j; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@Slf4j +public class MethodLevelAnnotationOperationsScanner + extends MethodLevelAnnotationScanner implements SimpleOperationsScanner.ClassProcessor { + + private final Class methodAnnotationClass; + private final PayloadClassExtractor payloadClassExtractor; + + public MethodLevelAnnotationOperationsScanner( + Class methodAnnotationClass, + BindingFactory bindingFactory, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + super(bindingFactory, schemasService); + this.methodAnnotationClass = methodAnnotationClass; + this.payloadClassExtractor = payloadClassExtractor; + } + + @Override + public Stream> process(Class clazz) { + log.debug( + "Scanning class \"{}\" for @\"{}\" annotated methods", + clazz.getName(), + methodAnnotationClass.getName()); + + return Arrays.stream(clazz.getDeclaredMethods()) + .filter(method -> !method.isBridge()) + .filter(method -> AnnotationUtil.findAnnotation(methodAnnotationClass, method) != null) + .map(this::mapMethodToOperation); + } + + private Map.Entry mapMethodToOperation(Method method) { + log.debug("Mapping method \"{}\" to operations", method.getName()); + + MethodAnnotation annotation = AnnotationUtil.findAnnotationOrThrow(methodAnnotationClass, method); + + String channelName = bindingFactory.getChannelName(annotation); + String operationId = channelName + "_" + OperationAction.RECEIVE + "_" + method.getName(); + Class payload = payloadClassExtractor.extractFrom(method); + + Operation operation = buildOperation(annotation, payload); + return Map.entry(operationId, operation); + } + + private Operation buildOperation(MethodAnnotation annotation, Class payloadType) { + MessageObject message = buildMessage(annotation, payloadType); + return buildOperation(annotation, message); + } + + private Operation buildOperation(MethodAnnotation annotation, MessageObject message) { + Map operationBinding = bindingFactory.buildOperationBinding(annotation); + Map opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null; + String channelName = bindingFactory.getChannelName(annotation); + + return Operation.builder() + .action(OperationAction.RECEIVE) + .channel(ChannelReference.fromChannel(channelName)) + .messages(List.of(MessageReference.toChannelMessage(channelName, message))) + .bindings(opBinding) + .build(); + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationScanner.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationScanner.java new file mode 100644 index 000000000..ef8bbf332 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationScanner.java @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; +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.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; + +import java.lang.annotation.Annotation; +import java.util.Map; + +@RequiredArgsConstructor +@Slf4j +public abstract class MethodLevelAnnotationScanner { + + protected final BindingFactory bindingFactory; + protected final SchemasService schemasService; + + protected MessageObject buildMessage(MethodAnnotation annotation, Class payloadType) { + Map messageBinding = bindingFactory.buildMessageBinding(annotation); + String modelName = schemasService.registerSchema(payloadType); + String headerModelName = schemasService.registerSchema(AsyncHeaders.NOT_DOCUMENTED); + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(modelName)) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(payloadType.getName()) + .name(payloadType.getName()) + .title(payloadType.getSimpleName()) + .description(null) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) + .bindings(messageBinding) + .build(); + + this.schemasService.registerMessage(message); + return message; + } +} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java index e795bb8ff..72e9c58fc 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AsyncMessage.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -9,37 +9,35 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import static io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message.DEFAULT_SCHEMA_FORMAT; - /** - * Annotation is mapped to {@link Message} + * Annotation is mapped to {@link MessageObject} */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) @Inherited public @interface AsyncMessage { /** - * Mapped to {@link Message#getDescription()} + * Mapped to {@link MessageObject#getDescription()} */ String description() default ""; /** - * Mapped to {@link Message#getMessageId()} + * Mapped to {@link MessageObject#getMessageId()} */ String messageId() default ""; /** - * Mapped to {@link Message#getName()} + * Mapped to {@link MessageObject#getName()} */ String name() default ""; /** - * Mapped to {@link Message#getSchemaFormat()} + * Mapped to {@link MessageObject#getContentType()} */ - String schemaFormat() default DEFAULT_SCHEMA_FORMAT; + String contentType() default "application/json"; /** - * Mapped to {@link Message#getTitle()} + * Mapped to {@link MessageObject#getTitle()} */ String title() default ""; } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AsyncAPI.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AsyncAPI.java index 4242ea24d..34dd2c37f 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AsyncAPI.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AsyncAPI.java @@ -1,24 +1,26 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2._6_0.model.Tag; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2._6_0.model.server.Server; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; -import java.util.Collections; import java.util.Map; -import java.util.Set; + +import static io.github.stavshamir.springwolf.asyncapi.v3.model.AsyncAPI.ASYNCAPI_DEFAULT_VERSION; /** - * This is the root document object for the API specification. It combines resource listing and API declaration together into one document. + * This is the root document object for the API specification. + * It combines resource listing and API declaration together into one document. * - * @see AsyncAPI specification + * @see AsyncAPI */ @Data @Builder @@ -26,41 +28,42 @@ @AllArgsConstructor public class AsyncAPI { /** - * Required. - * Specifies the AsyncAPI Specification version being used. - * It can be used by tooling Specifications and clients to interpret the version. - * The structure shall be major.minor.patch, where patch versions must be compatible - * with the existing major.minor tooling. - * Typically patch versions will be introduced to address errors in the documentation, - * and tooling should typically be compatible with the corresponding major.minor (1.0.*). + * REQUIRED. Specifies the AsyncAPI Specification version being used. It can be used by tooling Specifications and + * clients to interpret the version. The structure shall be major.minor.patch, where patch versions must be + * compatible with the existing major.minor tooling. Typically patch versions will be introduced to address errors + * in the documentation, and tooling should typically be compatible with the corresponding major.minor (1.0.*). * Patch versions will correspond to patches of this document. */ @NonNull @Builder.Default - private String asyncapi = "2.6.0"; + private String asyncapi = ASYNCAPI_DEFAULT_VERSION; /** - * Identifier of the application the AsyncAPI document is defining. - *

+ * Identifier of the + * application + * the AsyncAPI document is defining. + *

* This field represents a unique universal identifier of the application the AsyncAPI document is defining. * It must conform to the URI format, according to RFC3986. - *

+ *

* It is RECOMMENDED to use a URN to globally and uniquely identify the application during long periods of time, * even after it becomes unavailable or ceases to exist. */ private String id; /** - * Required. - * Provides metadata about the API. The metadata can be used by the clients if needed. + * REQUIRED. Provides metadata about the API. The metadata can be used by the clients if needed. */ @NonNull private Info info; /** + * Default content type to use when encoding/decoding a message's payload. + *

* A string representing the default content type to use when encoding/decoding a message's payload. - * The value MUST be a specific media type (e.g. application/json). - * This value MUST be used by schema parsers when the contentType property is omitted. + * The value MUST be a specific media type (e.g. application/json). This value MUST be used by schema + * parsers when the contentType property is omitted. + *

* In case a message can't be encoded/decoded using this value, schema parsers MUST use their default content type. */ private String defaultContentType; @@ -71,12 +74,25 @@ public class AsyncAPI { private Map servers; /** - * Required. - * The available channels and messages for the API. - * Channels are also known as "topics", "routing keys", "event types" or "paths". + * The channels used by this + * application. + *

+ * An identifier for the described channel. The channelId value is case-sensitive. Tools and libraries MAY + * use the channelId to uniquely identify a channel, therefore, it is RECOMMENDED to follow common programming + * naming conventions. */ @NonNull - private Map channels; + private Map channels; + + /** + * Holds a dictionary with all the operations this application MUST implement. + *

+ * If you're looking for a place to define operations that MAY or MAY NOT be implemented by the + * application + * consider defining them in components/operations. + */ + @JsonProperty(value = "operations") + private Map operations; /** * Holds a set of reusable objects for different aspects of the AsyncAPI specification. @@ -84,12 +100,4 @@ public class AsyncAPI { * referenced from properties outside the components object. */ private Components components; - - /** - * Required. - * A set of tags used by the specification with additional metadata. Each tag name in the set MUST be unique. - */ - @NonNull - @Builder.Default - private Set tags = Collections.emptySet(); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/Components.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/Components.java index f6975ccd3..03b904042 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/Components.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/Components.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; import io.swagger.v3.oas.models.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -23,4 +24,5 @@ public class Components { private Map schemas; + private Map messages; } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ConsumerData.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ConsumerData.java index 2261fe98d..ef1f57205 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ConsumerData.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ConsumerData.java @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.ServerReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -42,7 +43,7 @@ public class ConsumerData implements OperationData { */ @Nullable @Singular("server") - protected List servers; + protected List servers; /** * The channel binding of the producer. @@ -52,7 +53,7 @@ public class ConsumerData implements OperationData { * Map.of("kafka", new KafkaChannelBinding()) * */ - protected Map channelBinding; + protected Map channelBinding; /** * The class object of the payload published by this consumer. @@ -73,7 +74,7 @@ public class ConsumerData implements OperationData { * Map.of("kafka", new KafkaOperationBinding()) * */ - protected Map operationBinding; + protected Map operationBinding; /** * The message binding of the consumer. @@ -83,10 +84,10 @@ public class ConsumerData implements OperationData { * Map.of("kafka", new KafkaMessageBinding()) * */ - protected Map messageBinding; + protected Map messageBinding; /** * Operation message. */ - protected Message message; + protected MessageObject message; } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/OperationData.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/OperationData.java index 44ecd56b9..05eefe813 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/OperationData.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/OperationData.java @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.ServerReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import org.springframework.lang.Nullable; import java.util.List; @@ -30,12 +31,12 @@ public interface OperationData { * channel is available on all defined servers. May be null. */ @Nullable - List getServers(); + List getServers(); /** * The channel binding. */ - Map getChannelBinding(); + Map getChannelBinding(); /** * The class object of the payload. @@ -50,23 +51,12 @@ public interface OperationData { /** * The operation binding. */ - Map getOperationBinding(); + Map getOperationBinding(); /** * The message binding. */ - Map getMessageBinding(); + Map getMessageBinding(); - Message getMessage(); - - enum OperationType { - PUBLISH("publish"), - SUBSCRIBE("subscribe"); - - public final String operationName; - - OperationType(String operationName) { - this.operationName = operationName; - } - } + MessageObject getMessage(); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ProducerData.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ProducerData.java index bd355e0aa..7742e86be 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ProducerData.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/ProducerData.java @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.ServerReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -42,7 +43,7 @@ public class ProducerData implements OperationData { */ @Nullable @Singular("server") - protected List servers; + protected List servers; /** * The channel binding of the producer. @@ -52,7 +53,7 @@ public class ProducerData implements OperationData { * Map.of("kafka", new KafkaChannelBinding()) * */ - protected Map channelBinding; + protected Map channelBinding; /** * The class object of the payload published by this producer. @@ -73,7 +74,7 @@ public class ProducerData implements OperationData { * Map.of("kafka", new KafkaOperationBinding()) * */ - protected Map operationBinding; + protected Map operationBinding; /** * The message binding of the producer. @@ -83,10 +84,10 @@ public class ProducerData implements OperationData { * Map.of("kafka", new KafkaMessageBinding()) * */ - protected Map messageBinding; + protected Map messageBinding; /** * Operation message. */ - protected Message message; + protected MessageObject message; } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/bindings/EmptyChannelBinding.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/bindings/EmptyChannelBinding.java index a0b228ca5..ee261b4ca 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/bindings/EmptyChannelBinding.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/bindings/EmptyChannelBinding.java @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types.channel.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; public class EmptyChannelBinding extends ChannelBinding {} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/bindings/EmptyOperationBinding.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/bindings/EmptyOperationBinding.java index 234d863b6..9a3446030 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/bindings/EmptyOperationBinding.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/bindings/EmptyOperationBinding.java @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types.channel.operation.bindings; -import com.asyncapi.v2.binding.operation.OperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; public class EmptyOperationBinding extends OperationBinding {} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/Message.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/Message.java deleted file mode 100644 index da0168c9e..000000000 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/Message.java +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message; - -import com.asyncapi.v2.binding.message.MessageBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.Map; - -/** - * Describes a message received on a given channel and operation. - * - * @see Message specification - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Message { - - public static final String DEFAULT_SCHEMA_FORMAT = "application/vnd.oai.openapi+json;version=3.0.0"; - - @Builder.Default - private String schemaFormat = DEFAULT_SCHEMA_FORMAT; - - /** - * Unique string used to identify the message. - *

- * The id MUST be unique among all messages described in the API. The messageId value is case-sensitive. - * Tools and libraries MAY use the messageId to uniquely identify a message, therefore, it is RECOMMENDED to - * follow common programming naming conventions. - */ - private String messageId; - - /** - * A machine-friendly name for the message. - */ - private String name; - - /** - * A human-friendly title for the message. - */ - private String title; - - /** - * A human-friendly description for the message. - */ - private String description; - - private PayloadReference payload; - - private HeaderReference headers; - - private Map bindings; - - // Why do we add this empty class if Lombok @Builder is doing this job? Because this class is used as an argument - // in one method. Since Lombok works as an annotation Processor, the JavaDoc tool cannot find the generated class - // and fails. - // The alternative to define this class would be to use `delombok` during the Javadoc generation. This is really - // easy - // in Maven, but with Gradle seems to be more complicated. Creating an empty class that Lombok overrides and expands - // is much cleaner. - public static class MessageBuilder { - MessageBuilder() {} - } -} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/PayloadReference.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/PayloadReference.java deleted file mode 100644 index 204ccd334..000000000 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/PayloadReference.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@NoArgsConstructor -@EqualsAndHashCode -@ToString -public class PayloadReference { - - @Getter - @EqualsAndHashCode.Include - @ToString.Include - private String $ref; - - private PayloadReference(String $ref) { - this.$ref = $ref; - } - - public static PayloadReference fromModelName(String modelName) { - return new PayloadReference("#/components/schemas/" + modelName); - } -} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/bindings/EmptyMessageBinding.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/bindings/EmptyMessageBinding.java index aad02b8eb..3d7233754 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/bindings/EmptyMessageBinding.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/bindings/EmptyMessageBinding.java @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.bindings; -import com.asyncapi.v2.binding.message.MessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; public class EmptyMessageBinding extends MessageBinding {} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersCloudEventConstants.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersCloudEventConstants.java index 46f2a41d5..d407f7bf3 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersCloudEventConstants.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersCloudEventConstants.java @@ -16,4 +16,6 @@ public class AsyncHeadersCloudEventConstants { public static final String TIME_DESC = "CloudEvent Time Header"; public static final String TYPE = "ce_type"; public static final String TYPE_DESC = "CloudEvent Payload Type Header"; + + private AsyncHeadersCloudEventConstants() {} } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersNotDocumented.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersNotDocumented.java index e2b25b227..1c7020034 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersNotDocumented.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/AsyncHeadersNotDocumented.java @@ -10,6 +10,6 @@ public class AsyncHeadersNotDocumented implements AsyncHeadersBuilder { @Override public AsyncHeaders buildHeaders(Class payloadType) { - return AsyncHeaders.NOT_DOCUMENTED; + return NOT_DOCUMENTED; } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/HeaderReference.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/HeaderReference.java deleted file mode 100644 index 897290dde..000000000 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/channel/operation/message/header/HeaderReference.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@NoArgsConstructor -@EqualsAndHashCode -@ToString -public class HeaderReference { - - @Getter - @EqualsAndHashCode.Include - @ToString.Include - private String $ref; - - private HeaderReference(String $ref) { - this.$ref = $ref; - } - - public static HeaderReference fromModelName(String modelName) { - return new HeaderReference("#/components/schemas/" + modelName); - } -} diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/AsyncApiDocket.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/AsyncApiDocket.java index a164568e7..2486db330 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/AsyncApiDocket.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/AsyncApiDocket.java @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.configuration; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2._6_0.model.server.Server; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import lombok.Builder; import lombok.Data; import lombok.NonNull; diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketService.java index c7c2d2569..55f8593a4 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketService.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.configuration; -import com.asyncapi.v2._6_0.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigConstants; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import lombok.RequiredArgsConstructor; diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigConstants.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigConstants.java index 999231101..e37124bdf 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigConstants.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigConstants.java @@ -28,4 +28,6 @@ public class SpringwolfConfigConstants { SPRINGWOLF_SCANNER_PREFIX + ".producer-data" + ENABLED; public static final String SPRINGWOLF_SCHEMA_EXAMPLE_GENERATOR = SPRINGWOLF_CONFIG_PREFIX + ".example-generator"; + + private SpringwolfConfigConstants() {} } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigProperties.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigProperties.java index 74f8c8a51..c91e01bba 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigProperties.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/configuration/properties/SpringwolfConfigProperties.java @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.configuration.properties; -import com.asyncapi.v2._6_0.model.info.Contact; -import com.asyncapi.v2._6_0.model.info.License; -import com.asyncapi.v2._6_0.model.server.Server; +import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Contact; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.License; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import lombok.Getter; import lombok.Setter; @@ -82,7 +83,7 @@ public static class ConfigDocket { /** * Identifier of the application the AsyncAPI document is defining. * - * @see com.asyncapi.v2._6_0.model.AsyncAPI#id + * @see AsyncAPI#getId() */ @Nullable private String id; @@ -90,7 +91,7 @@ public static class ConfigDocket { /** * A string representing the default content type to use when encoding/decoding a message's payload. * - * @see com.asyncapi.v2._6_0.model.AsyncAPI#getdefaultContentType + * @see AsyncAPI#getDefaultContentType() */ @Nullable private String defaultContentType; @@ -101,7 +102,7 @@ public static class ConfigDocket { /** * The object provides metadata about the API. The metadata can be used by the clients if needed. * - * @see com.asyncapi.v2._6_0.model.info.Info + * @see Info */ @Nullable private Info info; @@ -113,7 +114,7 @@ public static class Info { /** * The title of the application * - * @see com.asyncapi.v2._6_0.model.info.Info#getTitle() + * @see io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info#getTitle() */ @Nullable private String title; @@ -121,7 +122,7 @@ public static class Info { /** * Required. Provides the version of the application API (not to be confused with the specification version). * - * @see com.asyncapi.v2._6_0.model.info.Info#getVersion() + * @see io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info#getVersion() */ @Nullable private String version; @@ -129,14 +130,14 @@ public static class Info { /** * A short description of the application. CommonMark syntax can be used for rich text representation. * - * @see com.asyncapi.v2._6_0.model.info.Info#getDescription() + * @see io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info#getDescription() */ @Nullable private String description; /** * A URL to the Terms of Service for the API. MUST be in the format of a URL. - * {@link com.asyncapi.v2._6_0.model.info.Info#getTermsOfService()} + * {@link io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info#getTermsOfService()} */ @Nullable private String termsOfService; diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasService.java index 7583acf4b..f4aa58bcd 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasService.java @@ -3,6 +3,9 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.AsyncApiPayload; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.postprocessor.SchemasPostProcessor; import io.swagger.v3.core.converter.ModelConverter; @@ -24,13 +27,15 @@ import java.util.function.Function; @Slf4j +// TODO: rename to DefaultComponentService & interface to ComponentService public class DefaultSchemasService implements SchemasService { private final ModelConverters converter = ModelConverters.getInstance(); private final List schemaPostProcessors; private final SpringwolfConfigProperties properties; - private final Map definitions = new HashMap<>(); + private final Map schemas = new HashMap<>(); + private final Map messages = new HashMap<>(); public DefaultSchemasService( List externalModelConverters, @@ -43,38 +48,52 @@ public DefaultSchemasService( } @Override - public Map getDefinitions() { - return definitions; + public Map getSchemas() { + return schemas; } @Override - public String register(AsyncHeaders headers) { + public String registerSchema(AsyncHeaders headers) { log.debug("Registering schema for {}", headers.getSchemaName()); MapSchema headerSchema = new MapSchema(); headerSchema.setName(headers.getSchemaName()); headerSchema.properties(headers); - this.definitions.put(headers.getSchemaName(), headerSchema); + this.schemas.put(headers.getSchemaName(), headerSchema); postProcessSchema(headerSchema); return headers.getSchemaName(); } @Override - public String register(Class type) { + public String registerSchema(Class type) { log.debug("Registering schema for {}", type.getSimpleName()); Map schemas = new LinkedHashMap<>(runWithFqnSetting((unused) -> converter.readAll(type))); String schemaName = getSchemaName(type, schemas); preProcessSchemas(schemas, schemaName, type); - this.definitions.putAll(schemas); + this.schemas.putAll(schemas); schemas.values().forEach(this::postProcessSchema); return schemaName; } + @Override + public Map getMessages() { + return this.messages; + } + + @Override + public MessageReference registerMessage(MessageObject message) { + log.debug("Registering message for {}", message.getName()); + + messages.put(message.getName(), message); + + return MessageReference.toComponentMessage(message); + } + private String getSchemaName(Class type, Map schemas) { if (schemas.isEmpty() && type.equals(String.class)) { return registerString(); @@ -124,7 +143,7 @@ private String registerString() { StringSchema schema = new StringSchema(); schema.setName(String.class.getName()); - this.definitions.put(schemaName, schema); + this.schemas.put(schemaName, schema); postProcessSchema(schema); return schemaName; @@ -145,6 +164,6 @@ private R runWithFqnSetting(Function callable) { } private void postProcessSchema(Schema schema) { - schemaPostProcessors.forEach(processor -> processor.process(schema, definitions)); + schemaPostProcessors.forEach(processor -> processor.process(schema, schemas)); } } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/SchemasService.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/SchemasService.java index 7ced4b101..08309ec60 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/SchemasService.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/SchemasService.java @@ -2,15 +2,22 @@ package io.github.stavshamir.springwolf.schemas; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import io.swagger.v3.oas.models.media.Schema; import java.util.Map; public interface SchemasService { - Map getDefinitions(); + Map getSchemas(); - String register(AsyncHeaders headers); + String registerSchema(AsyncHeaders headers); - String register(Class type); + String registerSchema(Class type); + + Map getMessages(); + + MessageReference registerMessage(MessageObject message); } diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleGenerator.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleGenerator.java index 425e50acb..e3767b3f3 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleGenerator.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/example/ExampleGenerator.java @@ -9,7 +9,7 @@ /** * Builds an example that is embedded into {@link Schema#setExample(Object)} * - * Handles types defined in https://www.asyncapi.com/docs/reference/specification/v2.6.0#dataTypeFormat + * Handles types defined in https://www.asyncapi.com/docs/reference/specification/v3.0.0#dataTypeFormat */ public interface ExampleGenerator { diff --git a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/postprocessor/SchemasPostProcessor.java b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/postprocessor/SchemasPostProcessor.java index 3cb450cf4..1c75936ed 100644 --- a/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/postprocessor/SchemasPostProcessor.java +++ b/springwolf-core/src/main/java/io/github/stavshamir/springwolf/schemas/postprocessor/SchemasPostProcessor.java @@ -8,7 +8,7 @@ /** * Internal interface to allow post-processing of a new schema (and their definition) after detection. *
- * It is closely coupled with the data structure of the SchemaService. + * It is closely coupled with the data structure of the SchemasService. */ public interface SchemasPostProcessor { void process(Schema schema, Map definitions); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/ClasspathUtil.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/ClasspathUtil.java new file mode 100644 index 000000000..cf2bec14d --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/ClasspathUtil.java @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.stavshamir.springwolf.asyncapi.v3.jackson.DefaultAsyncApiSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public final class ClasspathUtil { + private ClasspathUtil() {} + + public static String readAsString(String resourceName) throws IOException { + InputStream inputStream = ClasspathUtil.class.getResourceAsStream(resourceName); + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + + public static JsonNode parseYamlFile(String resourceName) throws IOException { + InputStream inputStream = ClasspathUtil.class.getResourceAsStream(resourceName); + ObjectMapper objectMapper = new DefaultAsyncApiSerializer().getYamlObjectMapper(); + return objectMapper.readTree(inputStream); + } +} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/SpringContextIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/SpringContextIntegrationTest.java index 5edb1c93e..3978490ac 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/SpringContextIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/SpringContextIntegrationTest.java @@ -14,7 +14,7 @@ @ExtendWith(SpringExtension.class) @MinimalIntegrationTestContextConfiguration -public class SpringContextIntegrationTest { +class SpringContextIntegrationTest { @Autowired private ApplicationContext context; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerServiceIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerServiceIntegrationTest.java index d91b612f5..653152b1f 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerServiceIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiSerializerServiceIntegrationTest.java @@ -1,23 +1,30 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.info.Contact; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2._6_0.model.info.License; -import com.asyncapi.v2._6_0.model.server.Server; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; -import com.asyncapi.v2.schema.Type; +import io.github.stavshamir.springwolf.ClasspathUtil; import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; import io.github.stavshamir.springwolf.asyncapi.types.Components; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ServerReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +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.info.Contact; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.License; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaType; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; import lombok.Data; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,7 +38,7 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {DefaultAsyncApiSerializerService.class}) @@ -58,36 +65,48 @@ private AsyncAPI getAsyncAPITestObject() { .build(); Server productionServer = Server.builder() - .url("development.gigantic-server.com") + .host("development.gigantic-server.com") .description("Development server") .protocol("kafka") .protocolVersion("1.0.0") .build(); - Message message = Message.builder() + MessageObject message = MessageObject.builder() .name("io.github.stavshamir.springwolf.ExamplePayload") .title("Example Payload") - .payload(PayloadReference.fromModelName("ExamplePayload")) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(MessageReference.toSchema("ExamplePayload")) + .build())) .bindings(Map.of( - "kafka", new KafkaMessageBinding(new StringSchema(), null, null, null, "binding-version-1"))) + "kafka", + KafkaMessageBinding.builder() + // FIXME: We should have a SchemaString (Schema) + .key(SchemaObject.builder().type("string").build()) + .build())) .build(); + Map messages = Map.of(message.getMessageId(), message); + + SchemaObject groupId = new SchemaObject(); + groupId.setEnumValues(List.of("myGroupId")); + groupId.setType(SchemaType.STRING); - com.asyncapi.v2.schema.Schema groupId = new com.asyncapi.v2.schema.Schema(); - groupId.setEnumValue(List.of("myGroupId")); - groupId.setType(Type.STRING); OperationBinding operationBinding = KafkaOperationBinding.builder().groupId(groupId).build(); Operation newUserOperation = Operation.builder() - .operationId("new-user_listenerMethod_subscribe") - .message(message) + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("new-user")) + .messages(List.of(MessageReference.toChannelMessage("new-user", message.getName()))) .bindings(Map.of("kafka", operationBinding)) .build(); - ChannelItem newUserChannel = ChannelItem.builder() + ChannelObject newUserChannel = ChannelObject.builder() + // FIXME: Can we autogenerate the address somehow? + .address("new-user") .description("This channel is used to exchange messages about users signing up") - .servers(List.of("production")) - .subscribe(newUserOperation) + .servers(List.of( + ServerReference.builder().ref("#/servers/production").build())) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); Map schemas = ModelConverters.getInstance() @@ -98,7 +117,9 @@ private AsyncAPI getAsyncAPITestObject() { .defaultContentType("application/json") .servers(Map.of("production", productionServer)) .channels(Map.of("new-user", newUserChannel)) - .components(Components.builder().schemas(schemas).build()) + .components( + Components.builder().schemas(schemas).messages(messages).build()) + .operations(Map.of("new-user_listenerMethod_subscribe", newUserOperation)) .build(); return asyncapi; @@ -110,16 +131,15 @@ void AsyncAPI_should_map_to_a_valid_asyncapi_json() throws IOException { String actual = serializer.toJsonString(asyncapi); InputStream s = this.getClass().getResourceAsStream("/asyncapi/asyncapi.json"); String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8); - assertEquals(expected, actual); + + assertThatJson(actual).isEqualTo(expected); } @Test void AsyncAPI_should_map_to_a_valid_asyncapi_yaml() throws IOException { var asyncapi = getAsyncAPITestObject(); - String actual = serializer.toYaml(asyncapi); - InputStream s = this.getClass().getResourceAsStream("/asyncapi/asyncapi.yaml"); - String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8); - assertEquals(expected, actual); + var expected = ClasspathUtil.parseYamlFile("/asyncapi/asyncapi.yaml"); + assertThatJson(serializer.toJsonString(asyncapi)).isEqualTo(expected); } @Data diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceIntegrationTest.java index 861780b2a..caa5927bb 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceIntegrationTest.java @@ -1,13 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2._6_0.model.server.Server; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.DefaultAsyncApiDocketService; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -49,13 +48,16 @@ "springwolf.docket.default-content-type=application/yaml", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) class DefaultAsyncApiServiceIntegrationTest { @MockBean private ChannelsService channelsService; + @MockBean + private OperationsService operationsService; + @MockBean private SchemasService schemasService; @@ -67,18 +69,14 @@ public void setUp() { when(channelsService.findChannels()) .thenReturn(Map.of( "consumer-topic", - ChannelItem.builder() - .subscribe(Operation.builder() - .bindings(Map.of("kafka", new KafkaOperationBinding())) - .message(Message.builder().build()) - .build()) + ChannelObject.builder() + .bindings(Map.of("kafka", new KafkaChannelBinding())) + .messages(Map.of("receiveId", MessageReference.toComponentMessage("receiveId"))) .build(), "producer-topic", - ChannelItem.builder() - .publish(Operation.builder() - .bindings(Map.of("kafka", new KafkaOperationBinding())) - .message(Message.builder().build()) - .build()) + ChannelObject.builder() + .bindings(Map.of("kafka", new KafkaChannelBinding())) + .messages(Map.of("sendId", MessageReference.toComponentMessage("sendId"))) .build())); } @@ -96,29 +94,31 @@ void getAsyncAPI_servers_should_be_correct() { Map actualServers = asyncApiService.getAsyncAPI().getServers(); assertThat(actualServers.get("test-protocol").getProtocol()).isEqualTo("test"); - assertThat(actualServers.get("test-protocol").getUrl()).isEqualTo("some-server:1234"); + assertThat(actualServers.get("test-protocol").getHost()).isEqualTo("some-server:1234"); } @Test void getAsyncAPI_channels_should_be_correct() { - Map actualChannels = asyncApiService.getAsyncAPI().getChannels(); + Map actualChannels = + asyncApiService.getAsyncAPI().getChannels(); assertThat(actualChannels).hasSize(2); assertThat(actualChannels).isNotEmpty().containsKey("consumer-topic"); - final ChannelItem consumerChannel = actualChannels.get("consumer-topic"); - assertThat(consumerChannel.getSubscribe()).isNotNull(); - assertThat(consumerChannel.getSubscribe().getBindings()) - .isEqualTo(Map.of("kafka", new KafkaOperationBinding())); - assertThat(((Message) consumerChannel.getSubscribe().getMessage()).getDescription()) - .isNull(); + final ChannelObject consumerChannel = actualChannels.get("consumer-topic"); + assertThat(consumerChannel.getBindings()).isEqualTo(Map.of("kafka", new KafkaChannelBinding())); + assertThat(consumerChannel.getMessages()).hasSize(1); + MessageReference receiveMessage = + (MessageReference) consumerChannel.getMessages().get("receiveId"); + assertThat(receiveMessage.getRef()).isEqualTo("#/components/messages/receiveId"); assertThat(actualChannels).isNotEmpty().containsKey("producer-topic"); - final ChannelItem publishChannel = actualChannels.get("producer-topic"); - assertThat(publishChannel.getPublish()).isNotNull(); - assertThat(publishChannel.getPublish().getBindings()).isEqualTo(Map.of("kafka", new KafkaOperationBinding())); - assertThat(((Message) publishChannel.getPublish().getMessage()).getDescription()) - .isNull(); + final ChannelObject publishChannel = actualChannels.get("producer-topic"); + assertThat(publishChannel.getBindings()).isEqualTo(Map.of("kafka", new KafkaChannelBinding())); + assertThat(publishChannel.getMessages()).hasSize(1); + MessageReference sendMessage = + (MessageReference) publishChannel.getMessages().get("sendId"); + assertThat(sendMessage.getRef()).isEqualTo("#/components/messages/sendId"); } @Order(TestDescriptionCustomizer.CUSTOMIZER_ORDER) diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceTest.java index 36e474734..172d22dc4 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultAsyncApiServiceTest.java @@ -21,11 +21,12 @@ /** * Pure unit tests of {@link DefaultAsyncApiService}. Faking spring context support */ -public class DefaultAsyncApiServiceTest { +class DefaultAsyncApiServiceTest { private DefaultAsyncApiService defaultAsyncApiService; private AsyncApiDocketService asyncApiDocketService; private ChannelsService channelsService; + private OperationsService operationsService; private SchemasService schemasService; private List customizers = new ArrayList<>(); @@ -33,13 +34,14 @@ public class DefaultAsyncApiServiceTest { public void setup() { asyncApiDocketService = mock(AsyncApiDocketService.class); channelsService = mock(ChannelsService.class); + operationsService = mock(OperationsService.class); schemasService = mock(SchemasService.class); when(channelsService.findChannels()).thenReturn(Map.of()); - when(schemasService.getDefinitions()).thenReturn(Map.of()); + when(schemasService.getSchemas()).thenReturn(Map.of()); - defaultAsyncApiService = - new DefaultAsyncApiService(asyncApiDocketService, channelsService, schemasService, customizers); + defaultAsyncApiService = new DefaultAsyncApiService( + asyncApiDocketService, channelsService, operationsService, schemasService, customizers); } @Test diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsServiceIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsServiceIntegrationTest.java index d67d09908..c559b5e17 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsServiceIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultChannelsServiceIntegrationTest.java @@ -1,9 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -19,10 +22,9 @@ @ContextConfiguration( classes = { DefaultChannelsService.class, - DefaultChannelsServiceIntegrationTest.FooChannelScanner.class, - DefaultChannelsServiceIntegrationTest.BarChannelScanner.class, - DefaultChannelsServiceIntegrationTest.SameTopic.SubscribeChannelScanner.class, - DefaultChannelsServiceIntegrationTest.SameTopic.ProduceChannelScanner.class + DefaultChannelsServiceIntegrationTest.SimpleChannelScanner.class, + DefaultChannelsServiceIntegrationTest.SameTopic.ReceiveChannelScanner.class, + DefaultChannelsServiceIntegrationTest.SameTopic.SendChannelScanner.class }) class DefaultChannelsServiceIntegrationTest { @@ -30,67 +32,67 @@ class DefaultChannelsServiceIntegrationTest { private DefaultChannelsService defaultChannelsService; @Autowired - private FooChannelScanner fooChannelScanner; - - @Autowired - private BarChannelScanner barChannelScanner; + private SimpleChannelScanner simpleChannelScanner; @Test void getChannels() { - Map actualChannels = defaultChannelsService.findChannels(); + Map actualChannels = defaultChannelsService.findChannels(); assertThat(actualChannels) - .containsAllEntriesOf(fooChannelScanner.scan()) - .containsAllEntriesOf(barChannelScanner.scan()) + .containsAllEntriesOf(simpleChannelScanner.scan()) .containsEntry(SameTopic.topicName, SameTopic.expectedMergedChannel); } @Component - static class FooChannelScanner implements ChannelsScanner { - @Override - public Map scan() { - return Map.of("foo", new ChannelItem()); - } - } - - @Component - static class BarChannelScanner implements ChannelsScanner { + static class SimpleChannelScanner implements ChannelsScanner { @Override - public Map scan() { - return Map.of("bar", new ChannelItem()); + public Map scan() { + return Map.of("foo", new ChannelObject()); } } static class SameTopic { - static final String topicName = "subscribeProduceTopic"; - static final ChannelItem expectedMergedChannel = ChannelItem.builder() - .publish(SameTopic.ProduceChannelScanner.publishOperation) - .subscribe(SameTopic.SubscribeChannelScanner.subscribeOperation) + static final String topicName = "receiveSendTopic"; + static final ChannelObject expectedMergedChannel = ChannelObject.builder() + .messages(Map.of( + "receiveMessage", + MessageReference.toComponentMessage("receiveMessage"), + "sendMessage", + MessageReference.toComponentMessage("sendMessage"))) .build(); @Component - static class ProduceChannelScanner implements ChannelsScanner { - static final Operation publishOperation = - Operation.builder().message("publish").build(); + static class SendChannelScanner implements ChannelsScanner { + static final Operation sentOperation = Operation.builder() + .channel(ChannelReference.fromChannel(topicName)) + .action(OperationAction.SEND) + .build(); @Override - public Map scan() { + public Map scan() { return Map.of( topicName, - ChannelItem.builder().publish(publishOperation).build()); + ChannelObject.builder() + .messages(Map.of("sendMessage", MessageReference.toComponentMessage("sendMessage"))) + .build()); } } @Component - static class SubscribeChannelScanner implements ChannelsScanner { - static final Operation subscribeOperation = - Operation.builder().message("consumer").build(); + static class ReceiveChannelScanner implements ChannelsScanner { + static final Operation receiveOperation = Operation.builder() + .channel(ChannelReference.fromChannel(topicName)) + .action(OperationAction.RECEIVE) + .build(); @Override - public Map scan() { + public Map scan() { return Map.of( topicName, - ChannelItem.builder().subscribe(subscribeOperation).build()); + ChannelObject.builder() + .messages( + Map.of("receiveMessage", MessageReference.toComponentMessage("receiveMessage"))) + .build()); } } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsServiceIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsServiceIntegrationTest.java new file mode 100644 index 000000000..b82bb9727 --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/DefaultOperationsServiceIntegrationTest.java @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi; + +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration( + classes = { + DefaultOperationsService.class, + DefaultOperationsServiceIntegrationTest.SimpleOperationScanner.class, + DefaultOperationsServiceIntegrationTest.SameTopic.ReceiveOperationScanner.class, + DefaultOperationsServiceIntegrationTest.SameTopic.SendOperationScanner.class + }) +class DefaultOperationsServiceIntegrationTest { + + @Autowired + private DefaultOperationsService defaultOperationsService; + + @Autowired + private SimpleOperationScanner simpleOperationsScanner; + + @Test + void getOperations() { + Map actualChannels = defaultOperationsService.findOperations(); + + assertThat(actualChannels) + .containsAllEntriesOf(simpleOperationsScanner.scan()) + .containsEntry("receive", SameTopic.ReceiveOperationScanner.receiveOperation) + .containsEntry("send", SameTopic.SendOperationScanner.sentOperation); + } + + @Component + static class SimpleOperationScanner implements OperationsScanner { + @Override + public Map scan() { + return Map.of( + "foo", + Operation.builder() + .channel(ChannelReference.fromChannel("foo")) + .action(OperationAction.RECEIVE) + .build()); + } + } + + static class SameTopic { + static final String topicName = "receiveSendTopic"; + + @Component + static class SendOperationScanner implements OperationsScanner { + static final Operation sentOperation = Operation.builder() + .channel(ChannelReference.fromChannel(topicName)) + .action(OperationAction.SEND) + .build(); + + @Override + public Map scan() { + return Map.of("send", sentOperation); + } + } + + @Component + static class ReceiveOperationScanner implements OperationsScanner { + static final Operation receiveOperation = Operation.builder() + .channel(ChannelReference.fromChannel(topicName)) + .action(OperationAction.RECEIVE) + .build(); + + @Override + public Map scan() { + return Map.of("receive", receiveOperation); + } + } + } +} 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 ea4037f93..d09410370 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,16 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; 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 io.github.stavshamir.springwolf.asyncapi.MessageHelper.toOperationsMessagesMap; +import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -18,110 +19,135 @@ class MessageHelperTest { @Test void toMessageObjectOrComposition_emptySet() { - assertThatThrownBy(() -> toMessageObjectOrComposition(Collections.emptySet())) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> toMessagesMap(Collections.emptySet())).isInstanceOf(IllegalArgumentException.class); } @Test - void toMessageObjectOrComposition_oneMessage() { - Message message = Message.builder().name("foo").build(); - - Object asObject = toMessageObjectOrComposition(Set.of(message)); - - assertThat(asObject).isInstanceOf(Message.class).isEqualTo(message); + void toOperationsMessageObjectOrComposition_emptySet() { + // When messages set is empty, the method should fail + assertThatThrownBy(() -> toOperationsMessagesMap("channel", Collections.emptySet())) + .isInstanceOf(IllegalArgumentException.class); } @Test - void toMessageObjectOrComposition_multipleMessages() { - Message message1 = Message.builder().name("foo").build(); - - Message message2 = Message.builder().name("bar").build(); - - Object asObject = toMessageObjectOrComposition(Set.of(message1, message2)); + void toOperationsMessageObjectOrComposition_invalidChannel() { + // When channel is not valid, the method should fail + MessageObject message = MessageObject.builder().name("foo").build(); - assertThat(asObject).isInstanceOf(Map.class).isEqualTo(Map.of("oneOf", List.of(message2, message1))); + assertThatThrownBy(() -> toOperationsMessagesMap(null, Set.of(message))) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> toOperationsMessagesMap("", Set.of(message))) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> toOperationsMessagesMap(" ", Set.of(message))) + .isInstanceOf(IllegalArgumentException.class); } @Test - void toMessageObjectOrComposition_multipleMessages_remove_duplicates() { - Message message1 = - Message.builder().name("foo").description("This is message 1").build(); + void toMessagesMap_oneMessage() { + MessageObject message = MessageObject.builder().name("foo").build(); - Message message2 = - Message.builder().name("bar").description("This is message 2").build(); + var messagesMap = toMessagesMap(Set.of(message)); - Message message3 = Message.builder() - .name("bar") - .description("This is message 3, but in essence the same payload type as message 2") - .build(); - - Object asObject = toMessageObjectOrComposition(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); + assertThat(messagesMap) + .containsExactlyInAnyOrderEntriesOf(Map.of("foo", MessageReference.toComponentMessage(message))); } @Test - void toMessageObjectOrComposition_multipleMessages_should_not_break_deep_equals() { - Message actualMessage1 = Message.builder() - .name("foo") - .description("This is actual message 1") - .build(); + void toOperationsMessagesMap_oneMessage() { + MessageObject message = MessageObject.builder().name("foo").build(); - Message actualMessage2 = Message.builder() - .name("bar") - .description("This is actual message 2") - .build(); + var messagesMap = toOperationsMessagesMap("channelName", Set.of(message)); - Object actualObject = toMessageObjectOrComposition(Set.of(actualMessage1, actualMessage2)); + assertThat(messagesMap) + .containsExactlyInAnyOrderEntriesOf( + Map.of("foo", MessageReference.toChannelMessage("channelName", message))); + } - Message expectedMessage1 = Message.builder() - .name("foo") - .description("This is expected message 1") - .build(); + @Test + void toMessagesMap_multipleMessages() { + MessageObject message1 = MessageObject.builder().name("foo").build(); - Message expectedMessage2 = Message.builder() - .name("bar") - .description("This is expected message 2") - .build(); + MessageObject message2 = MessageObject.builder().name("bar").build(); - Object expectedObject = toMessageObjectOrComposition(Set.of(expectedMessage1, expectedMessage2)); + var messages = toMessagesMap(Set.of(message1, message2)); - assertThat(actualObject).isNotEqualTo(expectedObject); + assertThat(messages) + .containsExactlyInAnyOrderEntriesOf(Map.of( + "bar", + MessageReference.toComponentMessage(message2), + "foo", + MessageReference.toComponentMessage(message1))); } @Test - void messageObjectToSet_notAMessageOrAMap() { - Object string = "foo"; + void toOperationsMessagesMap_multipleMessages() { + MessageObject message1 = MessageObject.builder().name("foo").build(); - Set messages = messageObjectToSet(string); + MessageObject message2 = MessageObject.builder().name("bar").build(); - assertThat(messages).isEmpty(); + var messages = toOperationsMessagesMap("channelName", Set.of(message1, message2)); + + assertThat(messages) + .containsExactlyInAnyOrderEntriesOf(Map.of( + "bar", + MessageReference.toChannelMessage("channelName", message2), + "foo", + MessageReference.toChannelMessage("channelName", message1))); } @Test - void messageObjectToSet_Message() { - Message message = Message.builder().name("foo").build(); - Object asObject = toMessageObjectOrComposition(Set.of(message)); + void toMessagesMap_multipleMessages_remove_duplicates() { + MessageObject message1 = MessageObject.builder() + .name("foo") + .description("This is message 1") + .build(); + + MessageObject message2 = MessageObject.builder() + .name("bar") + .description("This is message 2") + .build(); + + MessageObject message3 = MessageObject.builder() + .name("bar") + .description("This is message 3, but in essence the same payload type as message 2") + .build(); - Set messages = messageObjectToSet(asObject); + var messages = toMessagesMap(Set.of(message1, message2, message3)); - assertThat(messages).containsExactly(message); + // we do not have any guarantee whether message2 or message3 won the deduplication. + assertThat(messages) + .hasSize(2) + .containsValue(MessageReference.toComponentMessage(message1)) + .containsAnyOf( + entry("bar", MessageReference.toComponentMessage(message2)), + entry("bar", MessageReference.toComponentMessage(message3))); } @Test - void messageObjectToSet_SetOfMessage() { - Message message1 = Message.builder().name("foo").build(); + void toOperationsMessagesMap_multipleMessages_remove_duplicates() { + MessageObject message1 = MessageObject.builder() + .name("foo") + .description("This is message 1") + .build(); - Message message2 = Message.builder().name("bar").build(); + MessageObject message2 = MessageObject.builder() + .name("bar") + .description("This is message 2") + .build(); - Object asObject = toMessageObjectOrComposition(Set.of(message1, message2)); + MessageObject message3 = MessageObject.builder() + .name("bar") + .description("This is message 3, but in essence the same payload type as message 2") + .build(); - Set messages = messageObjectToSet(asObject); + var messages = toOperationsMessagesMap("channelName", Set.of(message1, message2, message3)); - assertThat(messages).containsExactlyInAnyOrder(message1, message2); + // we do not have any guarantee whether message2 or message3 won the deduplication. + assertThat(messages) + .hasSize(2) + .containsValue(MessageReference.toChannelMessage("channelName", message1)) + .containsAnyOf( + entry("bar", MessageReference.toChannelMessage("channelName", message2)), + entry("bar", MessageReference.toChannelMessage("channelName", message3))); } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java index 45475e537..a96cfbf29 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestAbstractOperationBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingProcessorPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.AsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java index 66cbfb894..8abf21621 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestMessageBindingProcessor.java @@ -1,10 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.MessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingProcessorPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.bindings.EmptyMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.MessageBinding; import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; @@ -20,7 +21,7 @@ public class TestMessageBindingProcessor implements MessageBindingProcessor { public static final String TYPE = "testType"; - public static final MessageBinding BINDING = new MessageBinding(); + public static final MessageBinding BINDING = new EmptyMessageBinding(); @Override public Optional process(Method method) { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java index e3e68e06a..9c7dc9c8c 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/TestOperationBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingProcessorPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding; import org.springframework.core.annotation.Order; import java.lang.annotation.ElementType; 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 3f600219c..d5d7e39f9 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 @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; import io.github.stavshamir.springwolf.asyncapi.MessageHelper; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -20,158 +20,118 @@ void shouldNotMergeDifferentChannelNames() { // given String channelName1 = "channel1"; String channelName2 = "channel2"; - Operation publishOperation = - Operation.builder().operationId("publisher").build(); - Operation subscribeOperation = - Operation.builder().operationId("subscribe").build(); - ChannelItem publisherChannel = - ChannelItem.builder().publish(publishOperation).build(); - ChannelItem subscriberChannel = - ChannelItem.builder().subscribe(subscribeOperation).build(); + ChannelObject publisherChannel = ChannelObject.builder().build(); + ChannelObject subscriberChannel = ChannelObject.builder().build(); // when - Map mergedChannels = ChannelMerger.merge( + Map mergedChannels = ChannelMerger.mergeChannels( Arrays.asList(Map.entry(channelName1, publisherChannel), Map.entry(channelName2, subscriberChannel))); // then - assertThat(mergedChannels) - .hasSize(2) - .hasEntrySatisfying(channelName1, it -> { - assertThat(it.getPublish()).isEqualTo(publishOperation); - assertThat(it.getSubscribe()).isNull(); - }) - .hasEntrySatisfying(channelName2, it -> { - assertThat(it.getPublish()).isNull(); - assertThat(it.getSubscribe()).isEqualTo(subscribeOperation); - }); + assertThat(mergedChannels).hasSize(2); } @Test - void shouldMergePublisherAndSubscriberIntoOneChannel() { + void shouldMergeEqualChannelNamesIntoOneChannel() { // given String channelName = "channel"; - Operation publishOperation = - Operation.builder().operationId("publisher").build(); - Operation subscribeOperation = - Operation.builder().operationId("subscribe").build(); - ChannelItem publisherChannel = - ChannelItem.builder().publish(publishOperation).build(); - ChannelItem subscriberChannel = - ChannelItem.builder().subscribe(subscribeOperation).build(); + ChannelObject publisherChannel = ChannelObject.builder().build(); + ChannelObject subscriberChannel = ChannelObject.builder().build(); // when - Map mergedChannels = ChannelMerger.merge( + Map mergedChannels = ChannelMerger.mergeChannels( Arrays.asList(Map.entry(channelName, publisherChannel), Map.entry(channelName, subscriberChannel))); // then - assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { - assertThat(it.getPublish()).isEqualTo(publishOperation); - assertThat(it.getSubscribe()).isEqualTo(subscribeOperation); - }); + assertThat(mergedChannels).hasSize(1); } @Test - void shouldUseFirstOperationFound() { + void shouldUseFirstChannelFound() { // given String channelName = "channel"; - Operation publishOperation1 = - Operation.builder().operationId("publisher1").build(); - Operation publishOperation2 = - Operation.builder().operationId("publisher2").build(); - ChannelItem publisherChannel1 = - ChannelItem.builder().publish(publishOperation1).build(); - ChannelItem publisherChannel2 = - ChannelItem.builder().publish(publishOperation2).build(); + ChannelObject publisherChannel1 = + ChannelObject.builder().title("channel1").build(); + ChannelObject publisherChannel2 = + ChannelObject.builder().title("channel2").build(); // when - Map mergedChannels = ChannelMerger.merge( + Map mergedChannels = ChannelMerger.mergeChannels( Arrays.asList(Map.entry(channelName, publisherChannel1), Map.entry(channelName, publisherChannel2))); // then assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { - assertThat(it.getPublish()).isEqualTo(publishOperation1); - assertThat(it.getSubscribe()).isNull(); + assertThat(it.getTitle()).isEqualTo("channel1"); }); } @Test - void shouldMergeDifferentMessageForSameOperation() { + void shouldMergeDifferentMessagesForSameChannel() { // given String channelName = "channel"; - Message message1 = Message.builder() + MessageObject message1 = MessageObject.builder() + .messageId("message1") .name(String.class.getCanonicalName()) .description("This is a string") .build(); - Message message2 = Message.builder() + MessageObject message2 = MessageObject.builder() + .messageId("message2") .name(Integer.class.getCanonicalName()) .description("This is an integer") .build(); - Message message3 = Message.builder() + MessageObject message3 = MessageObject.builder() + .messageId("message3") .name(Integer.class.getCanonicalName()) .description("This is also an integer, but in essence the same payload type") .build(); - Operation publishOperation1 = - Operation.builder().operationId("publisher1").message(message1).build(); - Operation publishOperation2 = - Operation.builder().operationId("publisher2").message(message2).build(); - Operation publishOperation3 = - Operation.builder().operationId("publisher3").message(message3).build(); - ChannelItem publisherChannel1 = - ChannelItem.builder().publish(publishOperation1).build(); - ChannelItem publisherChannel2 = - ChannelItem.builder().publish(publishOperation2).build(); - ChannelItem publisherChannel3 = - ChannelItem.builder().publish(publishOperation3).build(); + ChannelObject publisherChannel1 = ChannelObject.builder() + .messages(Map.of(message1.getName(), MessageReference.toComponentMessage(message1))) + .build(); + ChannelObject publisherChannel2 = ChannelObject.builder() + .messages(Map.of(message2.getName(), MessageReference.toComponentMessage(message2))) + .build(); + ChannelObject publisherChannel3 = ChannelObject.builder() + .messages(Map.of(message3.getName(), MessageReference.toComponentMessage(message3))) + .build(); // when - Map mergedChannels = ChannelMerger.merge(Arrays.asList( + Map mergedChannels = ChannelMerger.mergeChannels(Arrays.asList( Map.entry(channelName, publisherChannel1), Map.entry(channelName, publisherChannel2), Map.entry(channelName, publisherChannel3))); // 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)); + var expectedMessages = MessageHelper.toMessagesMap(Set.of(message1, message2)); assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { - assertThat(it.getPublish()) - .isEqualTo(Operation.builder() - .operationId("publisher1") - .message(expectedMessages) - .build()); - assertThat(it.getSubscribe()).isNull(); + assertThat(it.getMessages()).containsExactlyInAnyOrderEntriesOf(expectedMessages); }); } @Test - void shouldUseOtherMessageIfFirstMessageIsMissing() { + void shouldUseOtherMessageIfFirstMessageIsMissingForChannels() { // given String channelName = "channel"; - Message message2 = Message.builder() + MessageObject message2 = MessageObject.builder() + .messageId(String.class.getCanonicalName()) .name(String.class.getCanonicalName()) .description("This is a string") .build(); - Operation publishOperation1 = - Operation.builder().operationId("publisher1").build(); - Operation publishOperation2 = - Operation.builder().operationId("publisher2").message(message2).build(); - ChannelItem publisherChannel1 = - ChannelItem.builder().publish(publishOperation1).build(); - ChannelItem publisherChannel2 = - ChannelItem.builder().publish(publishOperation2).build(); + ChannelObject publisherChannel1 = ChannelObject.builder().build(); + ChannelObject publisherChannel2 = ChannelObject.builder() + .messages(Map.of(message2.getName(), message2)) + .build(); // when - Map mergedChannels = ChannelMerger.merge( + Map mergedChannels = ChannelMerger.mergeChannels( Arrays.asList(Map.entry(channelName, publisherChannel1), Map.entry(channelName, publisherChannel2))); // then expectedMessage message2 - Object expectedMessages = MessageHelper.toMessageObjectOrComposition(Set.of(message2)); + var expectedMessages = Map.of(message2.getName(), message2); + assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { - assertThat(it.getPublish()) - .isEqualTo(Operation.builder() - .operationId("publisher1") - .message(expectedMessages) - .build()); - assertThat(it.getSubscribe()).isNull(); + assertThat(it.getMessages()).hasSize(1); + assertThat(it.getMessages()).containsExactlyInAnyOrderEntriesOf(expectedMessages); }); } } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMergerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMergerTest.java new file mode 100644 index 000000000..75b8d6a82 --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/OperationMergerTest.java @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels; + +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class OperationMergerTest { + + @Test + void shouldNotMergeDifferentoperationIds() { + // given + String operationId1 = "operation1"; + String operationId2 = "operation2"; + Operation publisherOperation = Operation.builder().build(); + Operation subscriberOperation = Operation.builder().build(); + + // when + Map mergedOperations = OperationMerger.mergeOperations(Arrays.asList( + Map.entry(operationId1, publisherOperation), Map.entry(operationId2, subscriberOperation))); + + // then + assertThat(mergedOperations).hasSize(2); + } + + @Test + void shouldMergeEqualoperationIdsIntoOneOperation() { + // given + String operationId = "operation"; + Operation publishOperation = Operation.builder() + .action(OperationAction.SEND) + .title("publisher") + .build(); + Operation subscribeOperation = Operation.builder() + .action(OperationAction.RECEIVE) + .title("subscribe") + .build(); + + // when + Map mergedOperations = OperationMerger.mergeOperations( + Arrays.asList(Map.entry(operationId, publishOperation), Map.entry(operationId, subscribeOperation))); + + // then + assertThat(mergedOperations).hasSize(1); + } + + @Test + void shouldUseFirstOperationFound() { + // given + String operationId = "operation"; + Operation senderOperation = + Operation.builder().action(OperationAction.SEND).build(); + Operation receiverOperation = + Operation.builder().action(OperationAction.RECEIVE).build(); + + // when + Map mergedOperations = OperationMerger.mergeOperations( + Arrays.asList(Map.entry(operationId, senderOperation), Map.entry(operationId, receiverOperation))); + + // then + assertThat(mergedOperations).hasSize(1).hasEntrySatisfying(operationId, it -> { + assertThat(it.getAction()).isEqualTo(OperationAction.SEND); + }); + } + + @Test + void shouldMergeDifferentMessageForSameOperation() { + // given + String channelName = "channel"; + String operationId = "operation"; + MessageObject message1 = MessageObject.builder() + .messageId("message1") + .name(String.class.getCanonicalName()) + .description("This is a string") + .build(); + MessageObject message2 = MessageObject.builder() + .messageId("message2") + .name(Integer.class.getCanonicalName()) + .description("This is an integer") + .build(); + MessageObject message3 = MessageObject.builder() + .messageId("message3") + .name(Integer.class.getCanonicalName()) + .description("This is also an integer, but in essence the same payload type") + .build(); + MessageReference messageRef1 = MessageReference.toChannelMessage(channelName, message1); + MessageReference messageRef2 = MessageReference.toChannelMessage(channelName, message2); + MessageReference messageRef3 = MessageReference.toChannelMessage(channelName, message3); + + Operation senderOperation1 = Operation.builder() + .action(OperationAction.SEND) + .title("sender1") + .messages(List.of(messageRef1)) + .build(); + Operation senderOperation2 = Operation.builder() + .action(OperationAction.SEND) + .title("sender2") + .messages(List.of(messageRef2)) + .build(); + Operation senderOperation3 = Operation.builder() + .action(OperationAction.SEND) + .title("sender3") + .messages(List.of(messageRef3)) + .build(); + + // when + Map mergedOperations = OperationMerger.mergeOperations(List.of( + Map.entry(operationId, senderOperation1), + Map.entry(operationId, senderOperation2), + Map.entry(operationId, senderOperation3))); + + // 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 + assertThat(mergedOperations).hasSize(1).hasEntrySatisfying(operationId, it -> { + assertThat(it.getMessages()).containsExactlyInAnyOrder(messageRef1, messageRef2); + }); + } + + @Test + void shouldUseOtherMessageIfFirstMessageIsMissingForChannels() { + // given + String channelName = "channel"; + MessageObject message2 = MessageObject.builder() + .messageId(String.class.getCanonicalName()) + .name(String.class.getCanonicalName()) + .description("This is a string") + .build(); + ChannelObject publisherChannel1 = ChannelObject.builder().build(); + ChannelObject publisherChannel2 = ChannelObject.builder() + .messages(Map.of(message2.getName(), message2)) + .build(); + + // when + Map mergedChannels = ChannelMerger.mergeChannels( + Arrays.asList(Map.entry(channelName, publisherChannel1), Map.entry(channelName, publisherChannel2))); + + // then expectedMessage message2 + var expectedMessages = Map.of(message2.getName(), message2); + + assertThat(mergedChannels).hasSize(1).hasEntrySatisfying(channelName, it -> { + assertThat(it.getMessages()).hasSize(1); + assertThat(it.getMessages()).containsExactlyInAnyOrderEntriesOf(expectedMessages); + }); + } + + @Test + void shouldUseOtherMessageIfFirstMessageIsMissingForOperations() { + // given + String channelName = "channel-name"; + MessageObject message2 = MessageObject.builder() + .messageId(String.class.getCanonicalName()) + .name(String.class.getCanonicalName()) + .description("This is a string") + .build(); + Operation publishOperation1 = Operation.builder() + .action(OperationAction.SEND) + .title("publisher1") + .build(); + Operation publishOperation2 = Operation.builder() + .action(OperationAction.SEND) + .title("publisher2") + .messages(List.of(MessageReference.toChannelMessage(channelName, message2))) + .build(); + + // when + Map mergedOperations = OperationMerger.mergeOperations( + Arrays.asList(Map.entry("publisher1", publishOperation1), Map.entry("publisher1", publishOperation2))); + // then expectedMessage message2 + var expectedMessage = MessageReference.toChannelMessage(channelName, message2); + + assertThat(mergedOperations).hasSize(1).hasEntrySatisfying("publisher1", it -> { + assertThat(it.getMessages()).hasSize(1); + assertThat(it.getMessages()).containsExactlyInAnyOrder(expectedMessage); + }); + } +} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScannerTest.java index dbac812a4..9c0e8c365 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleChannelsScannerTest.java @@ -1,9 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; import org.junit.jupiter.api.Test; import java.util.Map; @@ -24,71 +23,57 @@ class SimpleChannelsScannerTest { private final SimpleChannelsScanner simpleChannelsScanner = new SimpleChannelsScanner(classScanner, classProcessor); @Test - public void noClassFoundTest() { + void noClassFoundTest() { // when - Map channels = simpleChannelsScanner.scan(); + Map channels = simpleChannelsScanner.scan(); // then assertThat(channels).isEmpty(); } @Test - public void processClassTest() { + void processClassTest() { // given when(classScanner.scan()).thenReturn(Set.of(String.class)); - Map.Entry channel1 = Map.entry( - "channel1", - ChannelItem.builder().publish(Operation.builder().build()).build()); - Map.Entry channel2 = Map.entry( - "channel2", - ChannelItem.builder().subscribe(Operation.builder().build()).build()); + Map.Entry channel1 = + Map.entry("channel1", ChannelObject.builder().build()); + Map.Entry channel2 = + Map.entry("channel2", ChannelObject.builder().build()); when(classProcessor.process(any())).thenReturn(Stream.of(channel1, channel2)); // when - Map channels = simpleChannelsScanner.scan(); + Map channels = simpleChannelsScanner.scan(); // then assertThat(channels).containsExactly(channel1, channel2); } @Test - public void sameChannelsAreMergedTest() { + void sameChannelsAreMergedTest() { // given when(classScanner.scan()).thenReturn(Set.of(String.class)); - Map.Entry channel1 = Map.entry( - "channel1", - ChannelItem.builder() - .publish(Operation.builder().operationId("pub").build()) - .build()); - Map.Entry channel2 = Map.entry( - "channel1", - ChannelItem.builder() - .subscribe(Operation.builder().operationId("sub").build()) - .build()); + Map.Entry channel1 = + Map.entry("channel1", ChannelObject.builder().build()); + Map.Entry channel2 = + Map.entry("channel1", ChannelObject.builder().build()); when(classProcessor.process(any())).thenReturn(Stream.of(channel1, channel2)); // when - Map channels = simpleChannelsScanner.scan(); + Map channels = simpleChannelsScanner.scan(); // then assertThat(channels) - .containsExactly(Map.entry( - "channel1", - ChannelItem.builder() - .publish(Operation.builder().operationId("pub").build()) - .subscribe( - Operation.builder().operationId("sub").build()) - .build())); + .containsExactly(Map.entry("channel1", ChannelObject.builder().build())); } @Test - public void processEmptyClassTest() { + void processEmptyClassTest() { // given when(classScanner.scan()).thenReturn(Set.of(String.class)); when(classProcessor.process(any())).thenReturn(Stream.of()); // when - Map channels = simpleChannelsScanner.scan(); + Map channels = simpleChannelsScanner.scan(); // then assertThat(channels).isEmpty(); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScannerTest.java new file mode 100644 index 000000000..770c92fce --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/SimpleOperationsScannerTest.java @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels; + +import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SimpleOperationsScannerTest { + + private final ClassScanner classScanner = mock(ClassScanner.class); + private final SimpleOperationsScanner.ClassProcessor classProcessor = + mock(SimpleOperationsScanner.ClassProcessor.class); + + private final SimpleOperationsScanner simpleOperationsScanner = + new SimpleOperationsScanner(classScanner, classProcessor); + + @Test + void noClassFoundTest() { + // when + Map operations = simpleOperationsScanner.scan(); + + // then + assertThat(operations).isEmpty(); + } + + @Test + void processClassTest() { + // given + when(classScanner.scan()).thenReturn(Set.of(String.class)); + Map.Entry operation1 = + Map.entry("operation1", Operation.builder().build()); + Map.Entry operation2 = + Map.entry("operation2", Operation.builder().build()); + when(classProcessor.process(any())).thenReturn(Stream.of(operation1, operation2)); + + // when + Map operations = simpleOperationsScanner.scan(); + + // then + assertThat(operations).containsExactly(operation2, operation1); + } + + @Test + void sameOperationsAreMergedTest() { + // given + when(classScanner.scan()).thenReturn(Set.of(String.class)); + Map.Entry operation1 = + Map.entry("operation1", Operation.builder().build()); + Map.Entry operation2 = + Map.entry("operation1", Operation.builder().build()); + when(classProcessor.process(any())).thenReturn(Stream.of(operation1, operation2)); + + // when + Map operations = simpleOperationsScanner.scan(); + + // then + assertThat(operations) + .containsExactly(Map.entry("operation1", Operation.builder().build())); + } + + @Test + void processEmptyClassTest() { + // given + when(classScanner.scan()).thenReturn(Set.of(String.class)); + when(classProcessor.process(any())).thenReturn(Stream.of()); + + // when + Map operations = simpleOperationsScanner.scan(); + + // then + assertThat(operations).isEmpty(); + } +} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtilTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtilTest.java index de5e6d993..db82323ed 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtilTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AnnotationUtilTest.java @@ -19,7 +19,7 @@ class AnnotationUtilTest { @Nested - public class FindAnnotationOrThrow { + class FindAnnotationOrThrow { @Test void findNoAnnotationTest() throws NoSuchMethodException { Method method = TestClass.class.getMethod("notAnnotatedMethod"); @@ -72,7 +72,7 @@ void findMetaAnnotationTest() throws NoSuchMethodException { } @Nested - public class FindAnnotation { + class FindAnnotation { @Test void findNoAnnotationTest() throws NoSuchMethodException { Method method = TestClass.class.getMethod("notAnnotatedMethod"); @@ -123,7 +123,7 @@ void findMetaAnnotationTest() throws NoSuchMethodException { } @Nested - public class FindAnnotations { + class FindAnnotations { @Test void findNoAnnotationTest() throws NoSuchMethodException { Method method = TestClass.class.getMethod("notAnnotatedMethod"); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScannerTest.java index 16a0aa651..325903449 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationChannelsScannerTest.java @@ -1,10 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2._6_0.model.server.Server; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.TestOperationBindingProcessor; @@ -13,11 +9,20 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; -import io.github.stavshamir.springwolf.asyncapi.types.OperationData; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ServerReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +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.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; @@ -53,8 +58,8 @@ class AsyncAnnotationChannelsScannerTest { - private AsyncAnnotationChannelsScanner.AsyncAnnotationProvider asyncAnnotationProvider = - new AsyncAnnotationChannelsScanner.AsyncAnnotationProvider<>() { + private final AsyncAnnotationScanner.AsyncAnnotationProvider asyncAnnotationProvider = + new AsyncAnnotationScanner.AsyncAnnotationProvider<>() { @Override public Class getAnnotation() { return AsyncListener.class; @@ -66,8 +71,8 @@ public AsyncOperation getAsyncOperation(AsyncListener annotation) { } @Override - public OperationData.OperationType getOperationType() { - return OperationData.OperationType.PUBLISH; + public OperationAction getOperationType() { + return OperationAction.SEND; } }; private final SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); @@ -120,40 +125,39 @@ private void setClassToScan(Class classToScan) { void scan_componentHasNoListenerMethods() { setClassToScan(ClassWithoutListenerAnnotation.class); - Map channels = channelScanner.scan(); + Map channels = channelScanner.scan(); assertThat(channels).isEmpty(); } @Test - void scan_componentHasListenerMethod() { + void scan_componentChannelHasListenerMethod() { // Given a class with methods annotated with AsyncListener, where only the channel-name is set setClassToScan(ClassWithListenerAnnotation.class); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) .description("SimpleFoo Message Description") - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .schemaFormat(Message.DEFAULT_SCHEMA_FORMAT) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(EMPTY_MAP) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId("test-channel_publish") - .bindings(EMPTY_MAP) - .message(message) + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); - ChannelItem expectedChannel = - ChannelItem.builder().bindings(null).publish(operation).build(); - assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } @@ -162,10 +166,10 @@ void scan_componentHasListenerMethodWithUnknownServer() { // Given a class with method annotated with AsyncListener, with an unknown servername setClassToScan(ClassWithListenerAnnotationWithInvalidServer.class); - assertThatThrownBy(() -> channelScanner.scan()) + assertThatThrownBy(channelScanner::scan) .isInstanceOf(IllegalArgumentException.class) .hasMessage( - "Operation 'test-channel_publish' defines unknown server ref 'server3'. This AsyncApi defines these server(s): [server1, server2]"); + "Operation 'test-channel_send' defines unknown server ref 'server3'. This AsyncApi defines these server(s): [server1, server2]"); } @Test @@ -174,30 +178,38 @@ void scan_componentHasListenerMethodWithAllAttributes() { setClassToScan(ClassWithListenerAnnotationWithAllAttributes.class); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) .description(null) - .schemaFormat(Message.DEFAULT_SCHEMA_FORMAT) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName("TestSchema")) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema("TestSchema"))) .bindings(EMPTY_MAP) .build(); - Operation operation = Operation.builder() - .description("description") - .operationId("test-channel_publish") - .bindings(Map.of(TestOperationBindingProcessor.TYPE, TestOperationBindingProcessor.BINDING)) - .message(message) + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .servers(List.of( + ServerReference.builder().ref("server1").build(), + ServerReference.builder().ref("server2").build())) .build(); - ChannelItem expectedChannel = ChannelItem.builder() - .bindings(null) - .servers(List.of("server1", "server2")) - .publish(operation) + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .title("test-channel_send") + .channel(ChannelReference.fromChannel("test-channel")) + .description("description") + .bindings(Map.of(TestOperationBindingProcessor.TYPE, TestOperationBindingProcessor.BINDING)) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) .build(); assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); @@ -209,36 +221,50 @@ void scan_componentHasMultipleListenerAnnotations() { setClassToScan(ClassWithMultipleListenerAnnotations.class); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel - Message.MessageBuilder builder = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .schemaFormat(Message.DEFAULT_SCHEMA_FORMAT) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(EMPTY_MAP); + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .description("SimpleFoo Message Description") + .build(); - Operation operation1 = Operation.builder() + Operation expectedOperation1 = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel-1")) .description("test-channel-1-description") - .operationId("test-channel-1_publish") + .title("test-channel-1_send") .bindings(EMPTY_MAP) - .message(builder.description("SimpleFoo Message Description").build()) + .messages(List.of(MessageReference.toChannelMessage("test-channel-1", message))) .build(); - ChannelItem expectedChannel1 = - ChannelItem.builder().bindings(null).publish(operation1).build(); + ChannelObject expectedChannel1 = ChannelObject.builder() + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .bindings(null) + .build(); - Operation operation2 = Operation.builder() + Operation expectedOperation2 = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel-2")) .description("test-channel-2-description") - .operationId("test-channel-2_publish") + .title("test-channel-2_send") .bindings(EMPTY_MAP) - .message(builder.description("SimpleFoo Message Description").build()) + .messages(List.of(MessageReference.toChannelMessage("test-channel-2", message))) .build(); - ChannelItem expectedChannel2 = - ChannelItem.builder().bindings(null).publish(operation2).build(); + ChannelObject expectedChannel2 = ChannelObject.builder() + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .bindings(null) + .build(); assertThat(actualChannels) .containsExactlyInAnyOrderEntriesOf(Map.of( @@ -252,29 +278,36 @@ void scan_componentHasAsyncMethodAnnotation() { setClassToScan(ClassWithMessageAnnotation.class); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() .messageId("simpleFoo") .name("SimpleFooPayLoad") .title("Message Title") .description("Message description") - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .schemaFormat("application/schema+json;version=draft-07") - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(EMPTY_MAP) .build(); - Operation operation = Operation.builder() + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) .description("test channel operation description") - .operationId("test-channel_publish") + .title("test-channel_send") .bindings(EMPTY_MAP) - .message(message) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) .build(); - ChannelItem expectedChannel = - ChannelItem.builder().bindings(null).publish(operation).build(); + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) + .build(); assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } @@ -345,7 +378,7 @@ private static class ClassWithMessageAnnotation { description = "Message description", messageId = "simpleFoo", name = "SimpleFooPayLoad", - schemaFormat = "application/schema+json;version=draft-07", + contentType = "application/schema+json;version=draft-07", title = "Message Title"))) private void methodWithAnnotation(SimpleFoo payload) {} @@ -361,28 +394,35 @@ void scan_componentHasOnlyDeclaredMethods(Class clazz) { setClassToScan(clazz); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel with the actual method, excluding type erased methods - Message message = Message.builder() + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) .description(null) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .schemaFormat("application/vnd.oai.openapi+json;version=3.0.0") - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(messagePayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(EMPTY_MAP) .build(); - Operation operation = Operation.builder() + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) .description("test channel operation description") - .operationId("test-channel_publish") + .title("test-channel_send") .bindings(EMPTY_MAP) - .message(message) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) .build(); - ChannelItem expectedChannel = - ChannelItem.builder().bindings(null).publish(operation).build(); + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .build(); assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } @@ -426,28 +466,36 @@ void scan_componentHasListenerMethodWithMetaAnnotation() { setClassToScan(ClassWithMetaAnnotation.class); // When scan is called - Map actualChannels = channelScanner.scan(); + Map actualChannels = channelScanner.scan(); // Then the returned collection contains the channel - Message message = Message.builder() + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) .description(null) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .schemaFormat("application/vnd.oai.openapi+json;version=3.0.0") - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(messagePayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(EMPTY_MAP) .build(); - Operation operation = Operation.builder() + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) .description("test channel operation description") - .operationId("test-channel_publish") + .title("test-channel_send") .bindings(EMPTY_MAP) - .message(message) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) .build(); - ChannelItem expectedChannel = - ChannelItem.builder().bindings(null).publish(operation).build(); + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .build(); assertThat(actualChannels).containsExactly(Map.entry("test-channel", expectedChannel)); } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScannerTest.java new file mode 100644 index 000000000..8ed34f92c --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationOperationsScannerTest.java @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.OperationBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.TestOperationBindingProcessor; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncMessage; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ClassScanner; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ServerReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +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.asyncapi.v3.model.server.Server; +import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; +import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; +import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; +import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +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.util.StringValueResolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.EMPTY_MAP; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AsyncAnnotationOperationsScannerTest { + + private final AsyncAnnotationScanner.AsyncAnnotationProvider asyncAnnotationProvider = + new AsyncAnnotationScanner.AsyncAnnotationProvider<>() { + @Override + public Class getAnnotation() { + return AsyncListener.class; + } + + @Override + public AsyncOperation getAsyncOperation(AsyncListener annotation) { + return annotation.operation(); + } + + @Override + public OperationAction getOperationType() { + return OperationAction.SEND; + } + }; + private final SpringwolfConfigProperties properties = new SpringwolfConfigProperties(); + private final ClassScanner classScanner = mock(ClassScanner.class); + private final SchemasService schemasService = new DefaultSchemasService(emptyList(), emptyList(), properties); + private final AsyncApiDocketService asyncApiDocketService = mock(AsyncApiDocketService.class); + private final PayloadClassExtractor payloadClassExtractor = new PayloadClassExtractor(properties); + + private final List operationBindingProcessors = + List.of(new TestOperationBindingProcessor()); + private final List messageBindingProcessors = emptyList(); + + private final StringValueResolver stringValueResolver = mock(StringValueResolver.class); + + private final AsyncAnnotationOperationsScanner operationsScanner = + new AsyncAnnotationOperationsScanner<>( + asyncAnnotationProvider, + classScanner, + schemasService, + payloadClassExtractor, + operationBindingProcessors, + messageBindingProcessors); + + @BeforeEach + public void setup() { + when(asyncApiDocketService.getAsyncApiDocket()) + .thenReturn(AsyncApiDocket.builder() + .info(new Info()) + .server("server1", new Server()) + .server("server2", new Server()) + .build()); + + operationsScanner.setEmbeddedValueResolver(stringValueResolver); + when(stringValueResolver.resolveStringValue(any())) + .thenAnswer(invocation -> switch ((String) invocation.getArgument(0)) { + case "${test.property.test-channel}" -> "test-channel"; + case "${test.property.description}" -> "description"; + case "${test.property.server1}" -> "server1"; + case "${test.property.server2}" -> "server2"; + default -> invocation.getArgument(0); + }); + } + + private void setClassToScan(Class classToScan) { + Set> classesToScan = singleton(classToScan); + when(classScanner.scan()).thenReturn(classesToScan); + } + + @Test + void scan_componentHasNoListenerMethods() { + setClassToScan(ClassWithoutListenerAnnotation.class); + + Map channels = operationsScanner.scan(); + + assertThat(channels).isEmpty(); + } + + @Test + void scan_componentOperationHasListenerMethod() { + // Given a class with methods annotated with AsyncListener, where only the channel-name is set + setClassToScan(ClassWithListenerAnnotation.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) + .name(SimpleFoo.class.getName()) + .title(SimpleFoo.class.getSimpleName()) + .description("SimpleFoo Message Description") + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .build(); + + Operation expectedOperation = Operation.builder() + .title("test-channel_send") + .action(OperationAction.SEND) + .description("Auto-generated description") + .channel(ChannelReference.fromChannel("test-channel")) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .bindings(EMPTY_MAP) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + } + + @Test + void scan_componentHasListenerMethodWithAllAttributes() { + // Given a class with method annotated with AsyncListener, where all attributes are set + setClassToScan(ClassWithListenerAnnotationWithAllAttributes.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .description(null) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema("TestSchema"))) + .bindings(EMPTY_MAP) + .build(); + + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .servers(List.of( + ServerReference.builder().ref("server1").build(), + ServerReference.builder().ref("server2").build())) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .title("test-channel_send") + .channel(ChannelReference.fromChannel("test-channel")) + .description("description") + .bindings(Map.of(TestOperationBindingProcessor.TYPE, TestOperationBindingProcessor.BINDING)) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + } + + @Test + void scan_componentHasMultipleListenerAnnotations() { + // Given a class with methods annotated with AsyncListener, where only the channel-name is set + setClassToScan(ClassWithMultipleListenerAnnotations.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) + .name(SimpleFoo.class.getName()) + .title(SimpleFoo.class.getSimpleName()) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .description("SimpleFoo Message Description") + .build(); + + Operation expectedOperation1 = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel-1")) + .description("test-channel-1-description") + .title("test-channel-1_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("test-channel-1", message))) + .build(); + + ChannelObject expectedChannel1 = ChannelObject.builder() + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .bindings(null) + .build(); + + Operation expectedOperation2 = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel-2")) + .description("test-channel-2-description") + .title("test-channel-2_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("test-channel-2", message))) + .build(); + + ChannelObject expectedChannel2 = ChannelObject.builder() + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .bindings(null) + .build(); + + assertThat(actualOperations) + .containsExactlyInAnyOrderEntriesOf(Map.of( + "test-channel-1_send_methodWithMultipleAnnotation", expectedOperation1, + "test-channel-2_send_methodWithMultipleAnnotation", expectedOperation2)); + } + + @Test + void scan_componentHasAsyncMethodAnnotation() { + // Given a class with methods annotated with AsyncListener, where only the channel-name is set + setClassToScan(ClassWithMessageAnnotation.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId("simpleFoo") + .name("SimpleFooPayLoad") + .title("Message Title") + .description("Message description") + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) + .description("test channel operation description") + .title("test-channel_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .build(); + + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("test-channel_send_methodWithAnnotation", expectedOperation)); + } + + private static class ClassWithoutListenerAnnotation { + + private void methodWithoutAnnotation() {} + } + + private static class ClassWithListenerAnnotation { + + @AsyncListener(operation = @AsyncOperation(channelName = "test-channel")) + private void methodWithAnnotation(SimpleFoo payload) {} + + private void methodWithoutAnnotation() {} + } + + private static class ClassWithListenerAnnotationWithInvalidServer { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description", + servers = {"server3"})) + private void methodWithAnnotation(SimpleFoo payload) {} + } + + private static class ClassWithListenerAnnotationWithAllAttributes { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "${test.property.test-channel}", + description = "${test.property.description}", + payloadType = String.class, + servers = {"${test.property.server1}", "${test.property.server2}"}, + headers = + @AsyncOperation.Headers( + schemaName = "TestSchema", + values = { + @AsyncOperation.Headers.Header(name = "header", value = "value") + }))) + @TestOperationBindingProcessor.TestOperationBinding() + private void methodWithAnnotation(SimpleFoo payload) {} + + private void methodWithoutAnnotation() {} + } + + private static class ClassWithMultipleListenerAnnotations { + + @AsyncListener( + operation = @AsyncOperation(channelName = "test-channel-1", description = "test-channel-1-description")) + @AsyncListener( + operation = @AsyncOperation(channelName = "test-channel-2", description = "test-channel-2-description")) + private void methodWithMultipleAnnotation(SimpleFoo payload) {} + } + + private static class ClassWithMessageAnnotation { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description", + message = + @AsyncMessage( + description = "Message description", + messageId = "simpleFoo", + name = "SimpleFooPayLoad", + contentType = "application/schema+json;version=draft-07", + title = "Message Title"))) + private void methodWithAnnotation(SimpleFoo payload) {} + + private void methodWithoutAnnotation() {} + } + + @Nested + class ImplementingInterface { + @ParameterizedTest + @ValueSource(classes = {ClassImplementingInterface.class, ClassImplementingInterfaceWithAnnotation.class}) + void scan_componentHasOnlyDeclaredMethods(Class clazz) { + // Given a class with a method, which is declared in a generic interface + setClassToScan(clazz); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel with the actual method, excluding type erased methods + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .description(null) + .payload(messagePayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) + .description("test channel operation description") + .title("test-channel_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .build(); + + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("test-channel_send_methodFromInterface", expectedOperation)); + } + + private static class ClassImplementingInterface implements ClassInterface { + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + @Override + public void methodFromInterface(String payload) {} + } + + interface ClassInterface { + void methodFromInterface(T payload); + } + + private static class ClassImplementingInterfaceWithAnnotation implements ClassInterfaceWithAnnotation { + + @Override + public void methodFromInterface(String payload) {} + } + + interface ClassInterfaceWithAnnotation { + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + void methodFromInterface(T payload); + } + } + + @Nested + class MetaAnnotation { + @Test + void scan_componentHasListenerMethodWithMetaAnnotation() { + // Given a class with methods annotated with a AsyncListener meta annotation + setClassToScan(ClassWithMetaAnnotation.class); + + // When scan is called + Map actualOperations = operationsScanner.scan(); + + // Then the returned collection contains the channel + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .description(null) + .payload(messagePayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(EMPTY_MAP) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.SEND) + .channel(ChannelReference.fromChannel("test-channel")) + .description("test channel operation description") + .title("test-channel_send") + .bindings(EMPTY_MAP) + .messages(List.of(MessageReference.toChannelMessage("test-channel", message))) + .build(); + + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(null) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) + .build(); + + assertThat(actualOperations) + .containsExactly(Map.entry("test-channel_send_methodFromInterface", expectedOperation)); + } + + public static class ClassWithMetaAnnotation { + @AsyncListenerMetaAnnotation + void methodFromInterface(String payload) {} + } + + @Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @AsyncListener( + operation = + @AsyncOperation( + channelName = "test-channel", + description = "test channel operation description")) + public @interface AsyncListenerMetaAnnotation {} + } + + @Data + @NoArgsConstructor + @Schema(description = "SimpleFoo Message Description") + private static class SimpleFoo { + private String s; + private boolean b; + } +} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtilTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtilTest.java index 88b4d77e7..44675ba7b 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtilTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/AsyncAnnotationScannerUtilTest.java @@ -1,16 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.TestAbstractOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.TestMessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.TestOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncMessage; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.message.MessageObject; import org.assertj.core.util.Maps; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -127,11 +127,11 @@ void processMessageFromAnnotationWithoutAsyncMessage(Class classWithOperation .thenAnswer(invocation -> invocation.getArgument(0).toString()); // when - Message.MessageBuilder actual = Message.builder(); + MessageObject.MessageObjectBuilder actual = MessageObject.builder(); AsyncAnnotationScannerUtil.processAsyncMessageAnnotation(actual, message, stringResolver); // then - var expectedMessage = Message.builder().build(); + var expectedMessage = MessageObject.builder().build(); assertEquals(expectedMessage, actual.build()); } @@ -150,15 +150,14 @@ void processMessageFromAnnotationWithAsyncMessage(Class classWithOperationBin .thenAnswer(invocation -> invocation.getArgument(0).toString()); // when - Message.MessageBuilder actual = Message.builder(); + MessageObject.MessageObjectBuilder actual = MessageObject.builder(); AsyncAnnotationScannerUtil.processAsyncMessageAnnotation(actual, message, stringResolver); // then - var expectedMessage = Message.builder() + var expectedMessage = MessageObject.builder() .description("Message description") .messageId("simpleFoo") .name("SimpleFooPayLoad") - .schemaFormat("application/schema+json;version=draft-07") .title("Message Title") .build(); assertEquals(expectedMessage, actual.build()); @@ -216,7 +215,7 @@ private void methodWithAnnotation(String payload) {} description = "Message description", messageId = "simpleFoo", name = "SimpleFooPayLoad", - schemaFormat = "application/schema+json;version=draft-07", + contentType = "application/schema+json;version=draft-07", title = "Message Title"))) @TestOperationBindingProcessor.TestOperationBinding() private void methodWithAsyncMessageAnnotation(String payload) {} @@ -259,7 +258,7 @@ private void methodWithAnnotation(String payload) {} description = "Message description", messageId = "simpleFoo", name = "SimpleFooPayLoad", - schemaFormat = "application/schema+json;version=draft-07", + contentType = "application/schema+json;version=draft-07", title = "Message Title"))) @TestAbstractOperationBindingProcessor.TestOperationBinding() private void methodWithAsyncMessageAnnotation(String payload) {} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerIntegrationTest.java index 5ffbef12a..95885078d 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerIntegrationTest.java @@ -1,17 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersNotDocumented; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +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.MessageHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +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.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -34,9 +36,7 @@ import java.lang.annotation.Target; import java.util.List; import java.util.Map; -import java.util.Set; -import static io.github.stavshamir.springwolf.asyncapi.MessageHelper.toMessageObjectOrComposition; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(SpringExtension.class) @@ -77,7 +77,7 @@ class NoClassListener { @Test void scan_componentHasNoClassLevelRabbitListenerAnnotation() { // when - List> channels = + List> channels = scanner.process(ClassWithoutClassListener.class).toList(); // then @@ -96,7 +96,7 @@ class NoMethodListener { @Test void scan_componentHasNoClassLevelRabbitListenerAnnotation() { // when - List> channels = + List> channels = scanner.process(ClassWithoutMethodListener.class).toList(); // then @@ -115,28 +115,27 @@ class OneMethodLevelAnnotation { @Test void scan_componentWithOneMethodLevelAnnotation() { // when - List> actualChannels = + List> actualChannels = scanner.process(ClassWithOneMethodLevelHandler.class).toList(); // then - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId("test-channel_publish_ClassWithOneMethodLevelHandler") - .bindings(TestBindingFactory.defaultOperationBinding) - .message(message) - .build(); - - ChannelItem expectedChannel = ChannelItem.builder() + ChannelObject expectedChannel = ChannelObject.builder() .bindings(TestBindingFactory.defaultChannelBinding) - .publish(operation) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); assertThat(actualChannels).containsExactly(Map.entry(TestBindingFactory.CHANNEL, expectedChannel)); @@ -158,36 +157,43 @@ class MultipleMethodLevelAnnotations { @Test void scan_componentWithMultipleRabbitHandlerMethods() { // when - List> actualChannels = + List> actualChannels = scanner.process(ClassWithMultipleMethodLevelHandlers.class).toList(); // Then the returned collection contains the channel with message set to oneOf - Message fooMessage = Message.builder() + MessagePayload simpleFooPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject fooMessage = MessageObject.builder() .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())) + .payload(simpleFooPayload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - Message barMessage = Message.builder() + MessagePayload stringPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject barMessage = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())) + .payload(stringPayload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId("test-channel_publish_ClassWithMultipleMethodLevelHandlers") - .bindings(TestBindingFactory.defaultOperationBinding) - .message(toMessageObjectOrComposition(Set.of(fooMessage, barMessage))) - .build(); - - ChannelItem expectedChannel = ChannelItem.builder() + ChannelObject expectedChannel = ChannelObject.builder() .bindings(TestBindingFactory.defaultChannelBinding) - .publish(operation) + .messages(Map.of( + fooMessage.getMessageId(), + MessageReference.toComponentMessage(fooMessage), + barMessage.getMessageId(), + MessageReference.toComponentMessage(barMessage))) .build(); assertThat(actualChannels).containsExactly(Map.entry(TestBindingFactory.CHANNEL, expectedChannel)); @@ -224,11 +230,11 @@ private static class SimpleFoo { static class TestBindingFactory implements BindingFactory { public static final String CHANNEL = "test-channel"; - public static final Map defaultMessageBinding = + public static final Map defaultMessageBinding = Map.of(CHANNEL, new TestBindingFactory.TestMessageBinding()); - public static final Map defaultChannelBinding = + public static final Map defaultChannelBinding = Map.of(CHANNEL, new TestBindingFactory.TestChannelBinding()); - public static final Map defaultOperationBinding = + public static final Map defaultOperationBinding = Map.of(CHANNEL, new TestBindingFactory.TestOperationBinding()); @Override @@ -237,17 +243,17 @@ public String getChannelName(TestClassListener annotation) { } @Override - public Map buildChannelBinding(TestClassListener annotation) { - return (Map) defaultChannelBinding; + public Map buildChannelBinding(TestClassListener annotation) { + return defaultChannelBinding; } @Override - public Map buildOperationBinding(TestClassListener annotation) { - return (Map) defaultOperationBinding; + public Map buildOperationBinding(TestClassListener annotation) { + return defaultOperationBinding; } @Override - public Map buildMessageBinding(TestClassListener annotation) { + public Map buildMessageBinding(TestClassListener annotation) { return defaultMessageBinding; } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerTest.java index fd6f7f22f..8a66b459d 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationChannelsScannerTest.java @@ -1,19 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersNotDocumented; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +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.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +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.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.Data; import lombok.NoArgsConstructor; @@ -24,7 +28,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -48,10 +51,12 @@ class ClassLevelAnnotationChannelsScannerTest { schemasService); private static final String CHANNEL = "test-channel"; - private static final Map defaultOperationBinding = Map.of("protocol", new AMQPOperationBinding()); - private static final Map defaultMessageBinding = + private static final Map defaultOperationBinding = + Map.of("protocol", new AMQPOperationBinding()); + private static final Map defaultMessageBinding = Map.of("protocol", new AMQPMessageBinding()); - private static final Map defaultChannelBinding = Map.of("protocol", new AMQPChannelBinding()); + private static final Map defaultChannelBinding = + Map.of("protocol", new AMQPChannelBinding()); @BeforeEach void setUp() { @@ -65,37 +70,35 @@ void setUp() { doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) .when(schemasService) - .register(any(Class.class)); + .registerSchema(any(Class.class)); doAnswer(invocation -> AsyncHeaders.NOT_DOCUMENTED.getSchemaName()) .when(schemasService) - .register(any(AsyncHeaders.class)); + .registerSchema(any(AsyncHeaders.class)); } @Test void scan_componentHasTestListenerMethods() { // when - List> channels = - scanner.process(ClassWithTestListenerAnnotation.class).collect(Collectors.toList()); + List> channels = + scanner.process(ClassWithTestListenerAnnotation.class).toList(); // then - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId(CHANNEL + "_publish_ClassWithTestListenerAnnotation") - .bindings(defaultOperationBinding) - .message(message) - .build(); - - ChannelItem expectedChannelItem = ChannelItem.builder() + ChannelObject expectedChannelItem = ChannelObject.builder() .bindings(defaultChannelBinding) - .publish(operation) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); assertThat(channels).containsExactly(Map.entry(CHANNEL, expectedChannelItem)); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScannerTest.java new file mode 100644 index 000000000..d79837b5f --- /dev/null +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/ClassLevelAnnotationOperationsScannerTest.java @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; + +import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersNotDocumented; +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.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +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.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ClassLevelAnnotationOperationsScannerTest { + + private final PayloadClassExtractor payloadClassExtractor = mock(PayloadClassExtractor.class); + private final BindingFactory bindingFactory = mock(BindingFactory.class); + private final SchemasService schemasService = mock(SchemasService.class); + ClassLevelAnnotationOperationsScanner scanner = + new ClassLevelAnnotationOperationsScanner<>( + TestClassListener.class, + TestMethodListener.class, + bindingFactory, + new AsyncHeadersNotDocumented(), + payloadClassExtractor, + schemasService); + + private static final String CHANNEL = "test-channel"; + private static final Map defaultOperationBinding = + Map.of("protocol", new AMQPOperationBinding()); + private static final Map defaultMessageBinding = + Map.of("protocol", new AMQPMessageBinding()); + private static final Map defaultChannelBinding = + Map.of("protocol", new AMQPChannelBinding()); + + @BeforeEach + void setUp() { + // when + when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL); + + doReturn(defaultOperationBinding).when(bindingFactory).buildOperationBinding(any()); + doReturn(defaultChannelBinding).when(bindingFactory).buildChannelBinding(any()); + doReturn(defaultMessageBinding).when(bindingFactory).buildMessageBinding(any()); + + doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); + doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) + .when(schemasService) + .registerSchema(any(Class.class)); + doAnswer(invocation -> AsyncHeaders.NOT_DOCUMENTED.getSchemaName()) + .when(schemasService) + .registerSchema(any(AsyncHeaders.class)); + } + + @Test + void scan_componentHasTestListenerMethods() { + // when + List> operations = + scanner.process(ClassWithTestListenerAnnotation.class).toList(); + + // then + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(defaultMessageBinding) + .build(); + + Operation expectedOperation = Operation.builder() + .action(OperationAction.RECEIVE) + .channel(ChannelReference.fromChannel(CHANNEL)) + .messages(List.of(MessageReference.toChannelMessage(CHANNEL, message))) + .bindings(Map.of("protocol", AMQPOperationBinding.builder().build())) + .build(); + String operationName = CHANNEL + "_receive_ClassWithTestListenerAnnotation"; + assertThat(operations).containsExactly(Map.entry(operationName, expectedOperation)); + } + + @TestClassListener + private static class ClassWithTestListenerAnnotation { + @TestMethodListener + private void methodWithAnnotation(String payload) {} + + private void methodWithoutAnnotation() {} + } + + @Data + @NoArgsConstructor + private static class SimpleFoo { + private String s; + private boolean b; + } + + @Retention(RetentionPolicy.RUNTIME) + @interface TestClassListener {} + + @Retention(RetentionPolicy.RUNTIME) + @interface TestMethodListener {} +} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerIntegrationTest.java index 68993915b..a1b12ed33 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerIntegrationTest.java @@ -1,17 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +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.MessageHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; +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.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -37,7 +39,6 @@ import static io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScannerIntegrationTest.TestBindingFactory.defaultChannelBinding; import static io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScannerIntegrationTest.TestBindingFactory.defaultMessageBinding; -import static io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScannerIntegrationTest.TestBindingFactory.defaultOperationBinding; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(SpringExtension.class) @@ -72,7 +73,7 @@ class NoListener { @Test void scan_componentHasNoListenerMethods() { // when - List> channels = + List> channels = scanner.process(ClassWithoutListenerAnnotation.class).toList(); // then @@ -89,28 +90,26 @@ class WithListener { @Test void scan_componentHasListenerMethod() { // when - List> actualChannels = + List> actualChannels = scanner.process(ClassWithListenerAnnotation.class).toList(); // then - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId("test-channel_publish_methodWithAnnotation") - .bindings(TestBindingFactory.defaultOperationBinding) - .message(message) - .build(); - - ChannelItem expectedChannel = ChannelItem.builder() + ChannelObject expectedChannel = ChannelObject.builder() .bindings(defaultChannelBinding) - .publish(operation) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); assertThat(actualChannels).containsExactly(Map.entry(TestBindingFactory.CHANNEL, expectedChannel)); @@ -130,48 +129,48 @@ class OneChannelTwoPayloads { @Test void scan_componentHasTestListenerMethods_multiplePayloads() { // when - List> channels = scanner.process( + List> channels = scanner.process( ClassWithTestListenerAnnotationMultiplePayloads.class) .toList(); // then - Message messageSimpleFoo = Message.builder() + MessagePayload simpleFooPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + MessagePayload stringPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject messageSimpleFoo = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(simpleFooPayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - Message messageString = Message.builder() + MessageObject messageString = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(stringPayload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(TestBindingFactory.defaultMessageBinding) .build(); - ChannelItem expectedChannelItem = ChannelItem.builder() + ChannelObject expectedChannelItem = ChannelObject.builder() + .messages(Map.of( + messageSimpleFoo.getMessageId(), MessageReference.toComponentMessage(messageSimpleFoo))) .bindings(defaultChannelBinding) - .publish(Operation.builder() - .description("Auto-generated description") - .operationId(TestBindingFactory.CHANNEL + "_publish_methodWithAnnotation") - .bindings(TestBindingFactory.defaultOperationBinding) - .message(messageSimpleFoo) - .build()) .build(); - ChannelItem expectedChannelItem2 = ChannelItem.builder() + ChannelObject expectedChannelItem2 = ChannelObject.builder() .bindings(defaultChannelBinding) - .publish(Operation.builder() - .description("Auto-generated description") - .operationId(TestBindingFactory.CHANNEL + "_publish_methodWithAnnotation") - .bindings(TestBindingFactory.defaultOperationBinding) - .message(messageString) - .build()) + .messages(Map.of(messageString.getMessageId(), MessageReference.toComponentMessage(messageString))) .build(); assertThat(channels) - .containsExactly( + .containsExactlyInAnyOrder( Map.entry(TestBindingFactory.CHANNEL, expectedChannelItem), Map.entry(TestBindingFactory.CHANNEL, expectedChannelItem2)); } @@ -191,28 +190,26 @@ class MetaAnnotation { @Test void scan_componentHasListenerMetaMethod() { // when - List> actualChannels = + List> actualChannels = scanner.process(ClassWithListenerMetaAnnotation.class).toList(); // then - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) .name(SimpleFoo.class.getName()) .title(SimpleFoo.class.getSimpleName()) - .payload(PayloadReference.fromModelName(SimpleFoo.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) .bindings(defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId("test-channel_publish_methodWithAnnotation") - .bindings(defaultOperationBinding) - .message(message) - .build(); - - ChannelItem expectedChannel = ChannelItem.builder() + ChannelObject expectedChannel = ChannelObject.builder() .bindings(defaultChannelBinding) - .publish(operation) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); assertThat(actualChannels).containsExactly(Map.entry(TestBindingFactory.CHANNEL, expectedChannel)); @@ -248,11 +245,11 @@ private static class SimpleFoo { static class TestBindingFactory implements BindingFactory { public static final String CHANNEL = "test-channel"; - public static final Map defaultMessageBinding = + public static final Map defaultMessageBinding = Map.of(CHANNEL, new TestBindingFactory.TestMessageBinding()); - public static final Map defaultChannelBinding = + public static final Map defaultChannelBinding = Map.of(CHANNEL, new TestBindingFactory.TestChannelBinding()); - public static final Map defaultOperationBinding = + public static final Map defaultOperationBinding = Map.of(CHANNEL, new TestBindingFactory.TestOperationBinding()); @Override @@ -261,17 +258,17 @@ public String getChannelName(TestChannelListener annotation) { } @Override - public Map buildChannelBinding(TestChannelListener annotation) { - return (Map) defaultChannelBinding; + public Map buildChannelBinding(TestChannelListener annotation) { + return defaultChannelBinding; } @Override - public Map buildOperationBinding(TestChannelListener annotation) { - return (Map) defaultOperationBinding; + public Map buildOperationBinding(TestChannelListener annotation) { + return defaultOperationBinding; } @Override - public Map buildMessageBinding(TestChannelListener annotation) { + public Map buildMessageBinding(TestChannelListener annotation) { return defaultMessageBinding; } diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerTest.java index fa4bcdcd8..860479a00 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/MethodLevelAnnotationChannelsScannerTest.java @@ -1,18 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.BindingFactory; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersNotDocumented; +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.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +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.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.Data; import lombok.NoArgsConstructor; @@ -41,13 +46,15 @@ class MethodLevelAnnotationChannelsScannerTest { TestListener.class, bindingFactory, payloadClassExtractor, schemasService); private static final String CHANNEL = "test-channel"; - private static final Map defaultOperationBinding = Map.of("protocol", new AMQPOperationBinding()); - private static final Map defaultMessageBinding = + private static final Map defaultOperationBinding = + Map.of("protocol", new AMQPOperationBinding()); + private static final Map defaultMessageBinding = Map.of("protocol", new AMQPMessageBinding()); - private static final Map defaultChannelBinding = Map.of("protocol", new AMQPChannelBinding()); + private static final Map defaultChannelBinding = + Map.of("protocol", new AMQPChannelBinding()); @BeforeEach - void setUp() { + void setUp() throws NoSuchMethodException { // when when(bindingFactory.getChannelName(any())).thenReturn(CHANNEL); @@ -58,37 +65,43 @@ void setUp() { doReturn(String.class).when(payloadClassExtractor).extractFrom(any()); doAnswer(invocation -> invocation.>getArgument(0).getSimpleName()) .when(schemasService) - .register(any(Class.class)); + .registerSchema(any(Class.class)); doAnswer(invocation -> AsyncHeaders.NOT_DOCUMENTED.getSchemaName()) .when(schemasService) - .register(any(AsyncHeaders.class)); + .registerSchema(any(AsyncHeaders.class)); + + var stringMethod = + ClassWithMultipleTestListenerAnnotation.class.getDeclaredMethod("methodWithAnnotation", String.class); + doReturn(String.class).when(payloadClassExtractor).extractFrom(stringMethod); + var simpleFooMethod = ClassWithMultipleTestListenerAnnotation.class.getDeclaredMethod( + "anotherMethodWithAnnotation", SimpleFoo.class); + doReturn(SimpleFoo.class).when(payloadClassExtractor).extractFrom(simpleFooMethod); } @Test void scan_componentHasTestListenerMethods() { // when - List> channels = + List> channels = scanner.process(ClassWithTestListenerAnnotation.class).collect(Collectors.toList()); // then - Message message = Message.builder() + MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + + MessageObject message = MessageObject.builder() + .messageId(String.class.getName()) .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) + .payload(payload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) .bindings(defaultMessageBinding) .build(); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId(CHANNEL + "_publish_methodWithAnnotation") - .bindings(defaultOperationBinding) - .message(message) - .build(); - - ChannelItem expectedChannelItem = ChannelItem.builder() + ChannelObject expectedChannelItem = ChannelObject.builder() .bindings(defaultChannelBinding) - .publish(operation) + .messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message))) .build(); assertThat(channels).containsExactly(Map.entry(CHANNEL, expectedChannelItem)); @@ -102,6 +115,64 @@ private void methodWithAnnotation(String payload) {} private void methodWithoutAnnotation() {} } + @Test + void scan_componentHasMultipleTestListenerMethods() { + // when + List> channels = + scanner.process(ClassWithMultipleTestListenerAnnotation.class).toList(); + + // then + MessagePayload stringPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build()); + MessagePayload simpleFooPayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(SimpleFoo.class.getSimpleName())) + .build()); + + MessageObject stringMessage = MessageObject.builder() + .messageId(String.class.getName()) + .name(String.class.getName()) + .title(String.class.getSimpleName()) + .payload(stringPayload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) + .bindings(defaultMessageBinding) + .build(); + + MessageObject simpleFooMessage = MessageObject.builder() + .messageId(SimpleFoo.class.getName()) + .name(SimpleFoo.class.getName()) + .title(SimpleFoo.class.getSimpleName()) + .payload(simpleFooPayload) + .headers(MessageHeaders.of( + MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName()))) + .bindings(defaultMessageBinding) + .build(); + + ChannelObject methodChannel = ChannelObject.builder() + .bindings(defaultChannelBinding) + .messages(Map.of(stringMessage.getMessageId(), MessageReference.toComponentMessage(stringMessage))) + .build(); + ChannelObject anotherMethodChannel = ChannelObject.builder() + .bindings(defaultChannelBinding) + .messages( + Map.of(simpleFooMessage.getMessageId(), MessageReference.toComponentMessage(simpleFooMessage))) + .build(); + + assertThat(channels) + .containsExactlyInAnyOrderElementsOf( + List.of(Map.entry(CHANNEL, methodChannel), Map.entry(CHANNEL, anotherMethodChannel))); + } + + private static class ClassWithMultipleTestListenerAnnotation { + + @TestListener + private void methodWithAnnotation(String payload) {} + + @TestListener + private void anotherMethodWithAnnotation(SimpleFoo payload) {} + } + @Data @NoArgsConstructor private static class SimpleFoo { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ComponentClassScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ComponentClassScannerIntegrationTest.java index 901cd186e..95f00e38d 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ComponentClassScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ComponentClassScannerIntegrationTest.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.classes; -import com.asyncapi.v2._6_0.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import org.junit.jupiter.api.BeforeEach; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ConfigurationClassScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ConfigurationClassScannerIntegrationTest.java index d1e28c0a1..bb745ae4c 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ConfigurationClassScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/ConfigurationClassScannerIntegrationTest.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.classes; -import com.asyncapi.v2._6_0.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import org.junit.jupiter.api.Test; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/SpringwolfClassScannerIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/SpringwolfClassScannerIntegrationTest.java index 3ce1bd8d6..e97e6ca52 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/SpringwolfClassScannerIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/classes/SpringwolfClassScannerIntegrationTest.java @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.classes; -import com.asyncapi.v2._6_0.model.info.Info; import io.github.stavshamir.springwolf.asyncapi.scanners.beans.DefaultBeanMethodsScanner; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import org.junit.jupiter.api.BeforeEach; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceIntegrationTest.java index b283bab65..2b866e1b2 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceIntegrationTest.java @@ -28,9 +28,9 @@ "springwolf.docket.info.extension-fields.x-api-name=api-name", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234" + "springwolf.docket.servers.test-protocol.host=some-server:1234" }) -public class DefaultAsyncApiDocketServiceIntegrationTest { +class DefaultAsyncApiDocketServiceIntegrationTest { @Autowired private DefaultAsyncApiDocketService asyncApiDocketService; diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceTest.java index 6317385bf..747ae50f9 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/DefaultAsyncApiDocketServiceTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.configuration; -import com.asyncapi.v2._6_0.model.info.Contact; -import com.asyncapi.v2._6_0.model.info.License; -import com.asyncapi.v2._6_0.model.server.Server; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Contact; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.License; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties.ConfigDocket; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties.ConfigDocket.Info; @@ -25,7 +25,7 @@ void testServiceShouldMapAllPropertiesToTheDocket() { configDocket.setDefaultContentType("application/json"); Server server = - Server.builder().protocol("some-protocol").url("some-url").build(); + Server.builder().protocol("some-protocol").host("some-url").build(); configDocket.setServers(newHashMap("some-protocol", server)); Info info = new Info(); @@ -73,7 +73,7 @@ void docketServiceShouldDeliverCachedDocket() { configDocket.setInfo(info); Server server = - Server.builder().protocol("some-protocol").url("some-url").build(); + Server.builder().protocol("some-protocol").host("some-url").build(); configDocket.setServers(newHashMap("some-protocol", server)); SpringwolfConfigProperties configProperties = new SpringwolfConfigProperties(); diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfConfigPropertiesIntegrationTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfConfigPropertiesIntegrationTest.java index f32cbb6cc..864ef570a 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfConfigPropertiesIntegrationTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfConfigPropertiesIntegrationTest.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.configuration; -import com.asyncapi.v2._6_0.model.server.Server; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -28,7 +28,7 @@ public class SpringwolfConfigPropertiesIntegrationTest { "springwolf.docket.info.extension-fields.x-api-name=api-name", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) static class TestSimplePropertiesIntegrationTest { @@ -61,7 +61,7 @@ void docketServersTest() { "test-protocol", Server.builder() .protocol("test") - .url("some-server:1234") + .host("some-server:1234") .build())); } } @@ -77,7 +77,7 @@ void docketServersTest() { "springwolf.docket.info.extension-fields.x-api-name=api-name", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) static class PayloadWithoutCustomizingIntegrationTest { @@ -109,7 +109,7 @@ void payloadTest() { "springwolf.docket.info.extension-fields.x-api-name=api-name", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.payload.extractable-classes.my.custom.class=1" }) static class PayloadWithCustomizingIntegrationTest { @@ -145,7 +145,7 @@ void payloadCustomizedTest() { "springwolf.docket.info.extension-fields.x-api-name=api-name", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.payload.extractable-classes.java.util.List=-1" }) static class PayloadDisabledIntegrationTest { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/AsyncApiDocketFixture.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/AsyncApiDocketFixture.java index e7b617ed6..d21eb9756 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/AsyncApiDocketFixture.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/AsyncApiDocketFixture.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.fixtures; -import com.asyncapi.v2._6_0.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; public class AsyncApiDocketFixture { diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/MinimalIntegrationTestContextConfiguration.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/MinimalIntegrationTestContextConfiguration.java index 644707e72..8233f2534 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/MinimalIntegrationTestContextConfiguration.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/fixtures/MinimalIntegrationTestContextConfiguration.java @@ -22,6 +22,6 @@ "springwolf.docket.default-content-type=application/yaml", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) public @interface MinimalIntegrationTestContextConfiguration {} diff --git a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasServiceTest.java b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasServiceTest.java index 41791a77f..68267ba72 100644 --- a/springwolf-core/src/test/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasServiceTest.java +++ b/springwolf-core/src/test/java/io/github/stavshamir/springwolf/schemas/DefaultSchemasServiceTest.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.AsyncApiPayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator; import io.github.stavshamir.springwolf.schemas.postprocessor.ExampleGeneratorPostProcessor; @@ -53,22 +54,46 @@ class DefaultSchemasServiceTest { new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter(" ", DefaultIndenter.SYS_LF)); @Test - void getDefinitions() throws IOException { - schemasService.register(CompositeFoo.class); - schemasService.register(FooWithEnum.class); + void getSchemas() throws IOException { + schemasService.registerSchema(CompositeFoo.class); + schemasService.registerSchema(FooWithEnum.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/definitions.json"); System.out.println("Got: " + actualDefinitions); assertEquals(expected, actualDefinitions); } + @Test + void getMessages() throws IOException { + schemasService.registerMessage( + MessageObject.builder().name("messageName1").build()); + schemasService.registerMessage( + MessageObject.builder().name("messageName2").build()); + + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getMessages()); + String expected = + """ + { + "messageName1" : { + "name" : "messageName1" + }, + "messageName2" : { + "name" : "messageName2" + } + }""" + .stripIndent(); + + System.out.println("Got: " + actualDefinitions); + assertEquals(expected, actualDefinitions); + } + @Test void getDocumentedDefinitions() throws IOException { - schemasService.register(DocumentedSimpleFoo.class); + schemasService.registerSchema(DocumentedSimpleFoo.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/documented-definitions.json"); System.out.println("Got: " + actualDefinitions); @@ -77,9 +102,9 @@ void getDocumentedDefinitions() throws IOException { @Test void getArrayDefinitions() throws IOException { - schemasService.register(ArrayFoo.class); + schemasService.registerSchema(ArrayFoo.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/array-definitions.json"); System.out.println("Got: " + actualDefinitions); @@ -88,9 +113,9 @@ void getArrayDefinitions() throws IOException { @Test void getComplexDefinitions() throws IOException { - schemasService.register(ComplexFoo.class); + schemasService.registerSchema(ComplexFoo.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/complex-definitions.json"); System.out.println("Got: " + actualDefinitions); @@ -99,9 +124,9 @@ void getComplexDefinitions() throws IOException { @Test void getListWrapperDefinitions() throws IOException { - schemasService.register(ListWrapper.class); + schemasService.registerSchema(ListWrapper.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/generics-wrapper-definitions.json"); System.out.println("Got: " + actualDefinitions); @@ -110,7 +135,7 @@ void getListWrapperDefinitions() throws IOException { @Test void classWithSchemaAnnotation() { - String modelName = schemasService.register(ClassWithSchemaAnnotation.class); + String modelName = schemasService.registerSchema(ClassWithSchemaAnnotation.class); assertThat(modelName).isEqualTo("DifferentName"); } @@ -126,9 +151,8 @@ void getDefinitionWithFqnClassName() throws IOException { // when Class clazz = OneFieldFooWithFqn.class; // swagger seems to cache results. Therefore, a new class must be used. - schemasServiceWithFqn.register(clazz); - String actualDefinitions = - objectMapper.writer(printer).writeValueAsString(schemasServiceWithFqn.getDefinitions()); + schemasServiceWithFqn.registerSchema(clazz); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasServiceWithFqn.getSchemas()); // then System.out.println("Got: " + actualDefinitions); @@ -139,7 +163,7 @@ void getDefinitionWithFqnClassName() throws IOException { @Test void postProcessorsAreCalled() { - schemasService.register(FooWithEnum.class); + schemasService.registerSchema(FooWithEnum.class); verify(schemasPostProcessor).process(any(), any()); } @@ -267,9 +291,9 @@ private static class MyClass { class SchemaWithOneOf { @Test void testSchemaWithOneOf() throws IOException { - schemasService.register(SchemaAnnotationFoo.class); + schemasService.registerSchema(SchemaAnnotationFoo.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/annotation-definitions.json"); System.out.println("Got: " + actualDefinitions); @@ -313,9 +337,9 @@ public class ImplementationTwo { class AsyncApiPayloadTest { @Test void stringEnvelopTest() throws IOException { - schemasService.register(StringEnvelop.class); + schemasService.registerSchema(StringEnvelop.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); String expected = jsonResource("/schemas/api-payload.json"); System.out.println("Got: " + actualDefinitions); @@ -326,9 +350,9 @@ void stringEnvelopTest() throws IOException { @Test void illegalEnvelopTest() throws IOException { - schemasService.register(EnvelopWithMultipleAsyncApiPayloadAnnotations.class); + schemasService.registerSchema(EnvelopWithMultipleAsyncApiPayloadAnnotations.class); - String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getDefinitions()); + String actualDefinitions = objectMapper.writer(printer).writeValueAsString(schemasService.getSchemas()); // fallback to EnvelopWithMultipleAsyncApiPayloadAnnotations, which contains the field assertThat(actualDefinitions).contains("otherField"); diff --git a/springwolf-core/src/test/resources/asyncapi/asyncapi.json b/springwolf-core/src/test/resources/asyncapi/asyncapi.json index 598ea8e19..04fb641d7 100644 --- a/springwolf-core/src/test/resources/asyncapi/asyncapi.json +++ b/springwolf-core/src/test/resources/asyncapi/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "AsyncAPI Sample App", "version": "1.0.1", @@ -18,7 +18,7 @@ "defaultContentType": "application/json", "servers": { "production": { - "url": "development.gigantic-server.com", + "host": "development.gigantic-server.com", "protocol": "kafka", "protocolVersion": "1.0.0", "description": "Development server" @@ -26,40 +26,18 @@ }, "channels": { "new-user": { + "address": "new-user", + "messages": { + "io.github.stavshamir.springwolf.ExamplePayload": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.ExamplePayload" + } + }, "description": "This channel is used to exchange messages about users signing up", "servers": [ - "production" - ], - "subscribe": { - "operationId": "new-user_listenerMethod_subscribe", - "bindings": { - "kafka": { - "groupId": { - "type": "string", - "enum": [ - "myGroupId" - ] - }, - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.ExamplePayload", - "title": "Example Payload", - "payload": { - "$ref": "#/components/schemas/ExamplePayload" - }, - "bindings": { - "kafka": { - "key": { - "type": "string" - }, - "bindingVersion": "binding-version-1" - } - } + { + "$ref": "#/servers/production" } - } + ] } }, "components": { @@ -72,7 +50,50 @@ } } } + }, + "messages": { + "io.github.stavshamir.springwolf.ExamplePayload": { + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/ExamplePayload" + } + }, + "name": "io.github.stavshamir.springwolf.ExamplePayload", + "title": "Example Payload", + "bindings": { + "kafka": { + "key": { + "type": "string" + }, + "bindingVersion": "0.4.0" + } + } + } } }, - "tags": [ ] + "operations": { + "new-user_listenerMethod_subscribe": { + "action": "send", + "channel": { + "$ref": "#/channels/new-user" + }, + "bindings": { + "kafka": { + "groupId": { + "type": "string", + "enum": [ + "myGroupId" + ] + }, + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/new-user/messages/io.github.stavshamir.springwolf.ExamplePayload" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-core/src/test/resources/asyncapi/asyncapi.yaml b/springwolf-core/src/test/resources/asyncapi/asyncapi.yaml index abf1d3f90..0a98129f9 100644 --- a/springwolf-core/src/test/resources/asyncapi/asyncapi.yaml +++ b/springwolf-core/src/test/resources/asyncapi/asyncapi.yaml @@ -1,4 +1,4 @@ -asyncapi: 2.6.0 +asyncapi: 3.0.0 info: title: AsyncAPI Sample App version: 1.0.1 @@ -14,35 +14,19 @@ info: defaultContentType: application/json servers: production: - url: development.gigantic-server.com + host: development.gigantic-server.com protocol: kafka protocolVersion: 1.0.0 description: Development server channels: new-user: + address: new-user + messages: + io.github.stavshamir.springwolf.ExamplePayload: + "$ref": "#/components/messages/io.github.stavshamir.springwolf.ExamplePayload" description: This channel is used to exchange messages about users signing up servers: - - production - subscribe: - operationId: new-user_listenerMethod_subscribe - bindings: - kafka: - groupId: - type: string - enum: - - myGroupId - bindingVersion: 0.4.0 - message: - schemaFormat: application/vnd.oai.openapi+json;version=3.0.0 - name: io.github.stavshamir.springwolf.ExamplePayload - title: Example Payload - payload: - $ref: '#/components/schemas/ExamplePayload' - bindings: - kafka: - key: - type: string - bindingVersion: binding-version-1 + - "$ref": "#/servers/production" components: schemas: ExamplePayload: @@ -50,4 +34,30 @@ components: properties: s: type: string -tags: [] + messages: + io.github.stavshamir.springwolf.ExamplePayload: + payload: + schemaFormat: application/vnd.aai.asyncapi+json;version=3.0.0 + schema: + "$ref": "#/components/schemas/ExamplePayload" + name: io.github.stavshamir.springwolf.ExamplePayload + title: Example Payload + bindings: + kafka: + key: + type: string + bindingVersion: 0.4.0 +operations: + new-user_listenerMethod_subscribe: + action: send + channel: + "$ref": "#/channels/new-user" + bindings: + kafka: + groupId: + type: string + enum: + - myGroupId + bindingVersion: 0.4.0 + messages: + - "$ref": "#/channels/new-user/messages/io.github.stavshamir.springwolf.ExamplePayload" diff --git a/springwolf-examples/springwolf-amqp-example/build.gradle b/springwolf-examples/springwolf-amqp-example/build.gradle index c66d1e6eb..6d278b6b8 100644 --- a/springwolf-examples/springwolf-amqp-example/build.gradle +++ b/springwolf-examples/springwolf-amqp-example/build.gradle @@ -15,6 +15,9 @@ dependencies { annotationProcessor project(":springwolf-plugins:springwolf-amqp") runtimeOnly project(":springwolf-ui") + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + runtimeOnly "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.amqp:spring-rabbit" 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 3ec9d7ed0..adf64fc4f 100644 --- a/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-amqp-example/src/main/resources/application.properties @@ -28,7 +28,7 @@ springwolf.docket.info.contact.extension-fields.x-phone=+49 123 456789 springwolf.docket.info.license.name=Apache License 2.0 springwolf.docket.info.license.extension-fields.x-desc=some description springwolf.docket.servers.amqp.protocol=amqp -springwolf.docket.servers.amqp.url=${spring.rabbitmq.host}:${spring.rabbitmq.port} +springwolf.docket.servers.amqp.host=${spring.rabbitmq.host}:${spring.rabbitmq.port} springwolf.use-fqn=true springwolf.plugin.amqp.publishing.enabled=true diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/ApiIntegrationTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/ApiIntegrationTest.java index c10541439..c89ade1fb 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/ApiIntegrationTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/ApiIntegrationTest.java @@ -17,7 +17,7 @@ @SpringBootTest( classes = {SpringwolfAmqpExampleApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class ApiIntegrationTest { +class ApiIntegrationTest { @Autowired private TestRestTemplate restTemplate; diff --git a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/SpringContextIntegrationTest.java index 7eb8e0ddd..a76b53f04 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-amqp-example/src/test/java/io/github/stavshamir/springwolf/example/amqp/SpringContextIntegrationTest.java @@ -23,7 +23,7 @@ public class SpringContextIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=amqp", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) class ApplicationPropertiesConfigurationTest { 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 b6c5f3fbb..b72809d6a 100644 --- a/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-amqp-example/src/test/resources/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - AMQP", "version": "1.0.0", @@ -20,50 +20,20 @@ "defaultContentType": "application/json", "servers": { "amqp": { - "url": "amqp:5672", + "host": "amqp:5672", "protocol": "amqp" } }, "channels": { "another-queue": { - "publish": { - "operationId": "another-queue_publish_receiveAnotherPayload", - "description": "Auto-generated description", - "bindings": { - "amqp": { - "cc": [ - "another-queue" - ], - "bindingVersion": "0.2.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" } }, "bindings": { "amqp": { "is": "queue", - "exchange": { - "name": "", - "type": "direct", - "durable": true, - "autoDelete": false, - "vhost": "/" - }, "queue": { "name": "another-queue", "durable": false, @@ -71,84 +41,26 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.2.0" + "bindingVersion": "0.3.0" } } }, "example-producer-channel-publisher": { - "subscribe": { - "operationId": "example-producer-channel-publisher_subscribe", - "description": "Custom, optional description defined in the AsyncPublisher annotation", - "bindings": { - "amqp": { - "expiration": 0, - "cc": [ ], - "priority": 0, - "deliveryMode": 0, - "mandatory": false, - "timestamp": false, - "ack": false, - "bindingVersion": "0.2.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "description": "Another payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" } } }, "example-queue": { - "publish": { - "operationId": "example-queue_publish_receiveExamplePayload", - "description": "Auto-generated description", - "bindings": { - "amqp": { - "cc": [ - "example-queue" - ], - "bindingVersion": "0.2.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" } }, "bindings": { "amqp": { "is": "queue", - "exchange": { - "name": "", - "type": "direct", - "durable": true, - "autoDelete": false, - "vhost": "/" - }, "queue": { "name": "example-queue", "durable": false, @@ -156,37 +68,14 @@ "autoDelete": false, "vhost": "/" }, - "bindingVersion": "0.2.0" + "bindingVersion": "0.3.0" } } }, "example-topic-routing-key": { - "publish": { - "operationId": "example-topic-routing-key_publish_bindingsExample", - "description": "Auto-generated description", - "bindings": { - "amqp": { - "cc": [ - "example-topic-routing-key" - ], - "bindingVersion": "0.2.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" } }, "bindings": { @@ -199,64 +88,17 @@ "autoDelete": false, "vhost": "/" }, - "queue": { - "name": "example-bindings-queue", - "durable": false, - "exclusive": true, - "autoDelete": true, - "vhost": "/" - }, - "bindingVersion": "0.2.0" + "bindingVersion": "0.3.0" } } }, "multi-payload-queue": { - "publish": { - "operationId": "multi-payload-queue_publish_bindingsBeanExample", - "description": "Auto-generated description", - "bindings": { - "amqp": { - "cc": [ - "example-topic-routing-key" - ], - "bindingVersion": "0.2.0" - } + "messages": { + "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" }, - "message": { - "oneOf": [ - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } - }, - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "amqp": { - "bindingVersion": "0.2.0" - } - } - } - ] + "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" } }, "bindings": { @@ -269,14 +111,7 @@ "autoDelete": false, "vhost": "/" }, - "queue": { - "name": "multi-payload-queue", - "durable": true, - "exclusive": false, - "autoDelete": false, - "vhost": "/" - }, - "bindingVersion": "0.2.0" + "bindingVersion": "0.3.0" } } } @@ -349,7 +184,154 @@ "someString": "some string value" } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.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.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "bindings": { + "amqp": { + "bindingVersion": "0.3.0" + } + } + }, + "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "bindings": { + "amqp": { + "bindingVersion": "0.3.0" + } + } + } } }, - "tags": [ ] + "operations": { + "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.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" + } + ] + }, + "example-producer-channel-publisher_send_sendMessage": { + "action": "send", + "channel": { + "$ref": "#/channels/example-producer-channel-publisher" + }, + "title": "example-producer-channel-publisher_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-producer-channel-publisher/messages/io.github.stavshamir.springwolf.example.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.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" + } + ] + }, + "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.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" + } + ] + }, + "multi-payload-queue_receive_bindingsBeanExample": { + "action": "receive", + "channel": { + "$ref": "#/channels/multi-payload-queue" + }, + "bindings": { + "amqp": { + "expiration": 0, + "cc": [ + "example-topic-routing-key" + ], + "bindingVersion": "0.3.0" + } + }, + "messages": [ + { + "$ref": "#/channels/multi-payload-queue/messages/io.github.stavshamir.springwolf.example.amqp.dtos.ExamplePayloadDto" + }, + { + "$ref": "#/channels/multi-payload-queue/messages/io.github.stavshamir.springwolf.example.amqp.dtos.AnotherPayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-cloud-stream-example/src/main/resources/application.properties b/springwolf-examples/springwolf-cloud-stream-example/src/main/resources/application.properties index eeb1f621c..539256f3e 100644 --- a/springwolf-examples/springwolf-cloud-stream-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-cloud-stream-example/src/main/resources/application.properties @@ -25,7 +25,7 @@ springwolf.docket.info.contact.email=example@example.com springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core springwolf.docket.info.license.name=Apache License 2.0 springwolf.docket.servers.kafka.protocol=kafka -springwolf.docket.servers.kafka.url=${spring.kafka.bootstrap-servers} +springwolf.docket.servers.kafka.host=${spring.kafka.bootstrap-servers} # For debugging purposes diff --git a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/ApiIntegrationTest.java b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/ApiIntegrationTest.java index 7dddbeadf..43c82b741 100644 --- a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/ApiIntegrationTest.java +++ b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/ApiIntegrationTest.java @@ -34,13 +34,13 @@ public class ApiIntegrationTest { void asyncApiResourceArtifactTest() throws IOException { String url = "/springwolf/docs"; String actual = restTemplate.getForObject(url, String.class); - Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actual); + // When running with EmbeddedKafka, the kafka bootstrap server does run on random ports + String actualPatched = actual.replace(bootstrapServers, "kafka:29092"); + Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); InputStream s = this.getClass().getResourceAsStream("/asyncapi.json"); - String expectedWithoutServersKafkaUrlPatch = new String(s.readAllBytes(), StandardCharsets.UTF_8); - // When running with EmbeddedKafka, the kafka bootstrap server does run on random ports - String expected = expectedWithoutServersKafkaUrlPatch.replace("kafka:29092", bootstrapServers); + String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8); - assertEquals(expected, actual); + assertEquals(expected, actualPatched); } } diff --git a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/SpringContextIntegrationTest.java index c15dd9716..1d505eddb 100644 --- a/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-cloud-stream-example/src/test/java/io/github/stavshamir/springwolf/example/cloudstream/SpringContextIntegrationTest.java @@ -29,7 +29,7 @@ public class SpringContextIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=kafka", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) class ApplicationPropertiesConfigurationTest { 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 f498ea243..d63f5b86e 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 @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - Cloud Stream", "version": "1.0.0", @@ -17,52 +17,15 @@ "defaultContentType": "application/json", "servers": { "kafka": { - "url": "kafka:29092", + "host": "kafka:29092", "protocol": "kafka" } }, "channels": { "another-topic": { - "subscribe": { - "operationId": "another-topic_subscribe_process", - "description": "Auto-generated description", - "bindings": { - "kafka": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { } - } - } - }, - "publish": { - "operationId": "another-topic_publish_consumerMethod", - "description": "Auto-generated description", - "bindings": { - "kafka": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto" } }, "bindings": { @@ -70,25 +33,9 @@ } }, "example-topic": { - "publish": { - "operationId": "example-topic_publish_process", - "description": "Auto-generated description", - "bindings": { - "kafka": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto" } }, "bindings": { @@ -164,7 +111,87 @@ "properties": { }, "example": { } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "bindings": { + "kafka": { } + } + }, + "io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "bindings": { + "kafka": { } + } + } } }, - "tags": [ ] + "operations": { + "another-topic_publish_consumerMethod": { + "action": "receive", + "channel": { + "$ref": "#/channels/another-topic" + }, + "description": "Auto-generated description", + "bindings": { + "kafka": { } + }, + "messages": [ + { + "$ref": "#/channels/another-topic/messages/io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto" + } + ] + }, + "another-topic_subscribe_process": { + "action": "send", + "channel": { + "$ref": "#/channels/another-topic" + }, + "description": "Auto-generated description", + "bindings": { + "kafka": { } + }, + "messages": [ + { + "$ref": "#/channels/another-topic/messages/io.github.stavshamir.springwolf.example.cloudstream.dtos.AnotherPayloadDto" + } + ] + }, + "example-topic_publish_process": { + "action": "receive", + "channel": { + "$ref": "#/channels/example-topic" + }, + "description": "Auto-generated description", + "bindings": { + "kafka": { } + }, + "messages": [ + { + "$ref": "#/channels/example-topic/messages/io.github.stavshamir.springwolf.example.cloudstream.dtos.ExamplePayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-jms-example/build.gradle b/springwolf-examples/springwolf-jms-example/build.gradle index 3118a410b..be0d8e5af 100644 --- a/springwolf-examples/springwolf-jms-example/build.gradle +++ b/springwolf-examples/springwolf-jms-example/build.gradle @@ -18,6 +18,9 @@ dependencies { compileOnly "jakarta.jms:jakarta.jms-api" + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}" diff --git a/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties b/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties index e4675a685..ee4fe48b8 100644 --- a/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-jms-example/src/main/resources/application.properties @@ -23,7 +23,7 @@ springwolf.docket.info.contact.email=example@example.com springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core springwolf.docket.info.license.name=Apache License 2.0 springwolf.docket.servers.jms.protocol=jms -springwolf.docket.servers.jms.url=${spring.artemis.broker-url} +springwolf.docket.servers.jms.host=${spring.artemis.broker-url} springwolf.use-fqn=true springwolf.plugin.jms.publishing.enabled=true diff --git a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java index 1b9202b03..04ea7bcd7 100644 --- a/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java +++ b/springwolf-examples/springwolf-jms-example/src/test/java/io/github/stavshamir/springwolf/example/jms/ApiIntegrationTest.java @@ -32,13 +32,12 @@ public class ApiIntegrationTest { void asyncApiResourceArtifactTest() 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("localhost:61616", "activemq:61616"); + Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); InputStream s = this.getClass().getResourceAsStream("/asyncapi.json"); - String expectedWithoutServersJmsUrlPatch = - new String(s.readAllBytes(), StandardCharsets.UTF_8).replace("\r\n", "\n"); - String expected = expectedWithoutServersJmsUrlPatch.replace("activemq:61616", "localhost:61616"); + String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8); - assertEquals(expected, actual); + assertEquals(expected, actualPatched); } } 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 f1368e997..fa7feec64 100644 --- a/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-jms-example/src/test/resources/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - JMS", "version": "1.0.0", @@ -17,85 +17,28 @@ "defaultContentType": "application/json", "servers": { "jms": { - "url": "tcp://activemq:61616", + "host": "tcp://activemq:61616", "protocol": "jms" } }, "channels": { "another-queue": { - "subscribe": { - "operationId": "another-queue_subscribe", - "description": "Custom, optional description defined in the AsyncPublisher annotation", - "bindings": { - "jms": { - "internal-field": "customValue", - "nested": { - "key": "nestedValue" - } - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "description": "Another payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "jms": { } - } - } - }, - "publish": { - "operationId": "another-queue_publish_receiveAnotherPayload", - "description": "Auto-generated description", - "bindings": { - "jms": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "jms": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" } } }, "example-queue": { - "publish": { - "operationId": "example-queue_publish_receiveExamplePayload", - "description": "Auto-generated description", - "bindings": { - "jms": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "jms": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto" } }, "bindings": { - "jms": { } + "jms": { + "bindingVersion": "0.0.1" + } } } }, @@ -167,7 +110,95 @@ "someString": "some string value" } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "bindings": { + "jms": { + "bindingVersion": "0.0.1" + } + } + }, + "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "bindings": { + "jms": { + "bindingVersion": "0.0.1" + } + } + } } }, - "tags": [ ] + "operations": { + "another-queue_receive_receiveAnotherPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/another-queue" + }, + "bindings": { + "jms": { } + }, + "messages": [ + { + "$ref": "#/channels/another-queue/messages/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" + } + ] + }, + "another-queue_send_sendMessage": { + "action": "send", + "channel": { + "$ref": "#/channels/another-queue" + }, + "title": "another-queue_send", + "description": "Custom, optional description defined in the AsyncPublisher annotation", + "bindings": { + "jms": { + "internal-field": "customValue", + "nested": { + "key": "nestedValue" + } + } + }, + "messages": [ + { + "$ref": "#/channels/another-queue/messages/io.github.stavshamir.springwolf.example.jms.dtos.AnotherPayloadDto" + } + ] + }, + "example-queue_receive_receiveExamplePayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/example-queue" + }, + "bindings": { + "jms": { } + }, + "messages": [ + { + "$ref": "#/channels/example-queue/messages/io.github.stavshamir.springwolf.example.jms.dtos.ExamplePayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-kafka-example/build.gradle b/springwolf-examples/springwolf-kafka-example/build.gradle index 9fea2cb58..5e23705c6 100644 --- a/springwolf-examples/springwolf-kafka-example/build.gradle +++ b/springwolf-examples/springwolf-kafka-example/build.gradle @@ -22,6 +22,9 @@ dependencies { annotationProcessor project(":springwolf-plugins:springwolf-kafka") + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + runtimeOnly "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework:spring-beans" diff --git a/springwolf-examples/springwolf-kafka-example/src/main/resources/application.properties b/springwolf-examples/springwolf-kafka-example/src/main/resources/application.properties index d69e67c8d..f22d024d8 100644 --- a/springwolf-examples/springwolf-kafka-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-kafka-example/src/main/resources/application.properties @@ -41,7 +41,7 @@ springwolf.use-fqn=true # Springwolf kafka configuration springwolf.docket.servers.kafka.protocol=kafka -springwolf.docket.servers.kafka.url=${spring.kafka.bootstrap-servers} +springwolf.docket.servers.kafka.host=${spring.kafka.bootstrap-servers} springwolf.plugin.kafka.publishing.enabled=true springwolf.plugin.kafka.publishing.producer.bootstrap-servers=${BOOTSTRAP_SERVER_SASL:localhost:9093} springwolf.plugin.kafka.publishing.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer diff --git a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/ApiIntegrationTest.java b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/ApiIntegrationTest.java index f11f309b0..564093760 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/ApiIntegrationTest.java +++ b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/ApiIntegrationTest.java @@ -34,13 +34,13 @@ public class ApiIntegrationTest { void asyncApiResourceArtifactTest() throws IOException { String url = "/springwolf/docs"; String actual = restTemplate.getForObject(url, String.class); - Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actual); + // When running with EmbeddedKafka, the kafka bootstrap server does run on random ports + String actualPatched = actual.replace(bootstrapServers, "kafka:29092"); + Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); InputStream s = this.getClass().getResourceAsStream("/asyncapi.json"); - String expectedWithoutServersKafkaUrlPatch = new String(s.readAllBytes(), StandardCharsets.UTF_8); - // When running with EmbeddedKafka, the kafka bootstrap server does run on random ports - String expected = expectedWithoutServersKafkaUrlPatch.replace("kafka:29092", bootstrapServers); + String expected = new String(s.readAllBytes(), StandardCharsets.UTF_8); - assertEquals(expected, actual); + assertEquals(expected, actualPatched); } } diff --git a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/OpenApiGeneratorSystemTest.java b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/OpenApiGeneratorSystemTest.java index 45a6f89f9..84a2c9292 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/OpenApiGeneratorSystemTest.java +++ b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/OpenApiGeneratorSystemTest.java @@ -22,13 +22,14 @@ class OpenApiGeneratorSystemTest { @Test void asyncApiResourceArtifactTest() throws IOException { InputStream expectedStream = this.getClass().getResourceAsStream("/asyncapi.json"); - String expectedWithoutServersKafkaUrlPatch = IOUtils.toString(expectedStream, StandardCharsets.UTF_8); - // When running with EmbeddedKafka, localhost is used as hostname - String expected = expectedWithoutServersKafkaUrlPatch.replace("kafka:29092", "localhost:9092"); + String expected = IOUtils.toString(expectedStream, StandardCharsets.UTF_8); InputStream actualStream = this.getClass().getResourceAsStream("/openapi-generated.json"); String actual = IOUtils.toString(actualStream, StandardCharsets.UTF_8) - .replace("\\u003d", "="); // openapi generator replaces equal (=) with its unicode representation + .replace("\\u003d", "=") // openapi generator replaces equal (=) with its unicode representation + .replace( + "localhost:9092", + "kafka:29092"); // When running with EmbeddedKafka, localhost is used as hostname // openapi generator uses a different formatter, therefore removing spaces and newlines during comparison assertEquals(expected.replaceAll("\\s", ""), actual.replaceAll("\\s", "")); diff --git a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/SpringContextIntegrationTest.java b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/SpringContextIntegrationTest.java index 795b2f6e0..4d1cbbef8 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/SpringContextIntegrationTest.java +++ b/springwolf-examples/springwolf-kafka-example/src/test/java/io/github/stavshamir/springwolf/example/kafka/SpringContextIntegrationTest.java @@ -29,7 +29,7 @@ public class SpringContextIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=kafka", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", }) class ApplicationPropertiesConfigurationTest { 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 91a6b6945..6b98fc765 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - Kafka", "version": "1.0.0", @@ -17,41 +17,15 @@ "defaultContentType": "application/json", "servers": { "kafka": { - "url": "kafka:29092", + "host": "kafka:29092", "protocol": "kafka" } }, "channels": { "another-topic": { - "publish": { - "operationId": "another-topic_publish_receiveAnotherPayloadBatched", - "description": "Auto-generated description", - "bindings": { - "kafka": { - "groupId": { - "type": "string", - "enum": [ - "example-group-id" - ] - }, - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" } }, "bindings": { @@ -61,56 +35,16 @@ } }, "avro-topic": { - "publish": { - "operationId": "avro-topic_publish", - "description": "Requires a running kafka-schema-registry. See docker-compose.yml to start it", - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto", - "title": "ExamplePayloadAvroDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto" } } }, "example-topic": { - "publish": { - "operationId": "example-topic_publish_receiveExamplePayload", - "description": "Auto-generated description", - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" } }, "bindings": { @@ -120,109 +54,22 @@ } }, "multi-payload-topic": { - "publish": { - "operationId": "multi-payload-topic_publish", - "description": "Override description in the AsyncListener annotation with servers at kafka:29092", - "bindings": { - "kafka": { - "groupId": { - "type": "string", - "enum": [ - "foo-groupId" - ] - }, - "clientId": { - "type": "string", - "enum": [ - "foo-clientId" - ] - }, - "bindingVersion": "0.4.0" - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" }, - "message": { - "oneOf": [ - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-AnotherPayloadDto" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } - }, - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-ExamplePayloadDto" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } - }, - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "javax.money.MonetaryAmount", - "title": "MonetaryAmount", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.addons.common_model_converters.converters.monetaryamount.MonetaryAmount" - }, - "headers": { - "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-MonetaryAmount" - }, - "bindings": { - "kafka": { - "key": { - "type": "string", - "description": "Kafka Consumer Message Key", - "example": "example-key" - }, - "bindingVersion": "0.4.0" - } - } - } - ] + "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" + }, + "javax.money.MonetaryAmount": { + "$ref": "#/components/messages/javax.money.MonetaryAmount" } } }, "protobuf-topic": { - "publish": { - "operationId": "protobuf-topic_publish_receiveExampleProtobufPayload", - "description": "Auto-generated description", - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message", - "title": "Message", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message" } }, "bindings": { @@ -232,88 +79,19 @@ } }, "string-topic": { - "publish": { - "operationId": "string-topic_publish", - "description": "Final classes (like String) can be documented using an envelope class and the @AsyncApiPayload annotation.", - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope" }, - "message": { - "oneOf": [ - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope", - "title": "StringEnvelope", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } - }, - { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "java.lang.String", - "title": "String", - "payload": { - "$ref": "#/components/schemas/String" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "kafka": { - "bindingVersion": "0.4.0" - } - } - } - ] + "java.lang.String": { + "$ref": "#/components/messages/java.lang.String" } } }, "topic-defined-via-asyncPublisher-annotation": { - "subscribe": { - "operationId": "topic-defined-via-asyncPublisher-annotation_subscribe", - "description": "Custom, optional description defined in the AsyncPublisher annotation", - "bindings": { - "kafka": { - "clientId": { - "type": "string", - "enum": [ - "foo-clientId" - ] - }, - "bindingVersion": "0.4.0" - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto", - "title": "NestedPayloadDto", - "description": "Payload model with nested complex types", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/SpringDefaultHeaderAndCloudEvent" - }, - "bindings": { - "kafka": { - "key": { - "type": "string", - "description": "Kafka Producer Message Key", - "example": "example-key" - }, - "bindingVersion": "0.4.0" - } - } + "messages": { + "io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto" } } } @@ -575,6 +353,8 @@ "type": "object", "properties": { "amount": { + "minimum": 0.1, + "exclusiveMinimum": true, "type": "number", "example": 99.99 }, @@ -592,6 +372,7 @@ "name": "io.github.stavshamir.springwolf.addons.common_model_converters.converters.monetaryamount.MonetaryAmount", "properties": { "amount": { + "minimum": 0.1, "name": "amount", "type": "number" }, @@ -915,7 +696,331 @@ "type": "object" } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope", + "title": "StringEnvelope", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto", + "title": "ExamplePayloadAvroDto", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message", + "title": "Message", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto": { + "headers": { + "$ref": "#/components/schemas/SpringDefaultHeaderAndCloudEvent" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto", + "title": "NestedPayloadDto", + "description": "Payload model with nested complex types", + "bindings": { + "kafka": { + "key": { + "type": "string", + "description": "Kafka Producer Message Key", + "examples": [ + "example-key" + ] + }, + "bindingVersion": "0.4.0" + } + } + }, + "java.lang.String": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/String" + } + }, + "name": "java.lang.String", + "title": "String", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + }, + "javax.money.MonetaryAmount": { + "headers": { + "$ref": "#/components/schemas/SpringKafkaDefaultHeaders-MonetaryAmount" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.addons.common_model_converters.converters.monetaryamount.MonetaryAmount" + } + }, + "name": "javax.money.MonetaryAmount", + "title": "MonetaryAmount", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + } + } } }, - "tags": [ ] + "operations": { + "another-topic_receive_receiveAnotherPayloadBatched": { + "action": "receive", + "channel": { + "$ref": "#/channels/another-topic" + }, + "bindings": { + "kafka": { + "groupId": { + "type": "string", + "enum": [ + "example-group-id" + ] + }, + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/another-topic/messages/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" + } + ] + }, + "avro-topic_receive_receiveExampleAvroPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/avro-topic" + }, + "title": "avro-topic_receive", + "description": "Requires a running kafka-schema-registry. See docker-compose.yml to start it", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/avro-topic/messages/io.github.stavshamir.springwolf.example.kafka.dto.avro.ExamplePayloadAvroDto" + } + ] + }, + "example-topic_receive_receiveExamplePayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/example-topic" + }, + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/example-topic/messages/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" + } + ] + }, + "multi-payload-topic_receive_ExampleClassLevelKafkaListener": { + "action": "receive", + "channel": { + "$ref": "#/channels/multi-payload-topic" + }, + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/multi-payload-topic/messages/io.github.stavshamir.springwolf.example.kafka.dtos.AnotherPayloadDto" + }, + { + "$ref": "#/channels/multi-payload-topic/messages/io.github.stavshamir.springwolf.example.kafka.dtos.ExamplePayloadDto" + }, + { + "$ref": "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" + } + ] + }, + "multi-payload-topic_receive_receiveMonetaryAmount": { + "action": "receive", + "channel": { + "$ref": "#/channels/multi-payload-topic" + }, + "title": "multi-payload-topic_receive", + "description": "Override description in the AsyncListener annotation with servers at kafka:29092", + "bindings": { + "kafka": { + "groupId": { + "type": "string", + "enum": [ + "foo-groupId" + ] + }, + "clientId": { + "type": "string", + "enum": [ + "foo-clientId" + ] + }, + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/multi-payload-topic/messages/javax.money.MonetaryAmount" + } + ] + }, + "protobuf-topic_receive_receiveExampleProtobufPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/protobuf-topic" + }, + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/protobuf-topic/messages/io.github.stavshamir.springwolf.example.kafka.dto.proto.ExamplePayloadProtobufDto$Message" + } + ] + }, + "string-topic_receive_receiveStringPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/string-topic" + }, + "title": "string-topic_receive", + "description": "Final classes (like String) can be documented using an envelope class and the @AsyncApiPayload annotation.", + "bindings": { + "kafka": { + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/string-topic/messages/io.github.stavshamir.springwolf.example.kafka.consumers.StringConsumer$StringEnvelope" + }, + { + "$ref": "#/channels/string-topic/messages/java.lang.String" + } + ] + }, + "topic-defined-via-asyncPublisher-annotation_send_sendMessage": { + "action": "send", + "channel": { + "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation" + }, + "title": "topic-defined-via-asyncPublisher-annotation_send", + "description": "Custom, optional description defined in the AsyncPublisher annotation", + "bindings": { + "kafka": { + "clientId": { + "type": "string", + "enum": [ + "foo-clientId" + ] + }, + "bindingVersion": "0.4.0" + } + }, + "messages": [ + { + "$ref": "#/channels/topic-defined-via-asyncPublisher-annotation/messages/io.github.stavshamir.springwolf.example.kafka.dtos.NestedPayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-sns-example/build.gradle b/springwolf-examples/springwolf-sns-example/build.gradle index a8187c09e..a199b26e6 100644 --- a/springwolf-examples/springwolf-sns-example/build.gradle +++ b/springwolf-examples/springwolf-sns-example/build.gradle @@ -22,6 +22,9 @@ dependencies { runtimeOnly project(":springwolf-add-ons:springwolf-json-schema") runtimeOnly project(":springwolf-ui") + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + runtimeOnly "org.springframework.boot:spring-boot-starter-web" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" diff --git a/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/consumers/ExampleConsumer.java b/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/consumers/ExampleConsumer.java index d00c9ddaa..0635ecfd7 100644 --- a/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/consumers/ExampleConsumer.java +++ b/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/consumers/ExampleConsumer.java @@ -4,6 +4,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncListener; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBindingIdentifier; import io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto; import io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto; import io.github.stavshamir.springwolf.example.sns.producers.AnotherProducer; @@ -18,7 +19,7 @@ public class ExampleConsumer { private final AnotherProducer anotherProducer; @AsyncListener(operation = @AsyncOperation(channelName = "example-topic")) - @SnsAsyncOperationBinding + @SnsAsyncOperationBinding(protocol = "sqs", endpoint = @SnsAsyncOperationBindingIdentifier()) public void receiveExamplePayload(ExamplePayloadDto payload) { log.info("Received new message in example-topic: {}", payload.toString()); @@ -30,7 +31,7 @@ public void receiveExamplePayload(ExamplePayloadDto payload) { } @AsyncListener(operation = @AsyncOperation(channelName = "another-topic")) - @SnsAsyncOperationBinding + @SnsAsyncOperationBinding(protocol = "sqs", endpoint = @SnsAsyncOperationBindingIdentifier()) public void receiveAnotherPayload(AnotherPayloadDto payload) { log.info("Received new message in another-topic: {}", payload.toString()); } diff --git a/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/producers/AnotherProducer.java b/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/producers/AnotherProducer.java index 4d500477a..a6b3e03c6 100644 --- a/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/producers/AnotherProducer.java +++ b/springwolf-examples/springwolf-sns-example/src/main/java/io/github/stavshamir/springwolf/example/sns/producers/AnotherProducer.java @@ -5,6 +5,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBindingIdentifier; import io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto; import lombok.RequiredArgsConstructor; import org.springframework.messaging.support.MessageBuilder; @@ -22,7 +23,7 @@ public class AnotherProducer { @AsyncOperation( channelName = TOPIC, description = "Custom, optional description defined in the AsyncPublisher annotation")) - @SnsAsyncOperationBinding + @SnsAsyncOperationBinding(protocol = "sqs", endpoint = @SnsAsyncOperationBindingIdentifier()) public void sendMessage(AnotherPayloadDto msg) { template.send(TOPIC, MessageBuilder.withPayload(msg).build()); } diff --git a/springwolf-examples/springwolf-sns-example/src/main/resources/application.properties b/springwolf-examples/springwolf-sns-example/src/main/resources/application.properties index bd191de07..123cee570 100644 --- a/springwolf-examples/springwolf-sns-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-sns-example/src/main/resources/application.properties @@ -26,7 +26,7 @@ springwolf.docket.info.contact.email=example@example.com springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core springwolf.docket.info.license.name=Apache License 2.0 springwolf.docket.servers.sns.protocol=sns -springwolf.docket.servers.sns.url=http://localhost:4566 +springwolf.docket.servers.sns.host=http://localhost:4566 springwolf.use-fqn=true springwolf.plugin.sns.publishing.enabled=true 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 f5008280e..eed8129d4 100644 --- a/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-sns-example/src/test/resources/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - SNS", "version": "1.0.0", @@ -17,78 +17,22 @@ "defaultContentType": "application/json", "servers": { "sns": { - "url": "http://localhost:4566", + "host": "http://localhost:4566", "protocol": "sns" } }, "channels": { "another-topic": { - "subscribe": { - "operationId": "another-topic_subscribe", - "description": "Custom, optional description defined in the AsyncPublisher annotation", - "bindings": { - "sns": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "description": "Another payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sns": { } - } - } - }, - "publish": { - "operationId": "another-topic_publish", - "description": "Auto-generated description", - "bindings": { - "sns": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "description": "Another payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sns": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" } } }, "example-topic": { - "publish": { - "operationId": "example-topic_publish", - "description": "Auto-generated description", - "bindings": { - "sns": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "description": "Example payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sns": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto" } } } @@ -247,7 +191,122 @@ "type": "object" } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "description": "Another payload model", + "bindings": { + "sns": { } + } + }, + "io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "description": "Example payload model", + "bindings": { + "sns": { } + } + } } }, - "tags": [ ] + "operations": { + "another-topic_receive_receiveAnotherPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/another-topic" + }, + "title": "another-topic_receive", + "description": "Auto-generated description", + "bindings": { + "sns": { + "consumers": [ + { + "protocol": "sqs", + "endpoint": { }, + "filterPolicyScope": "MessageAttributes", + "rawMessageDelivery": true + } + ], + "bindingVersion": "0.1.0" + } + }, + "messages": [ + { + "$ref": "#/channels/another-topic/messages/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" + } + ] + }, + "another-topic_send_sendMessage": { + "action": "send", + "channel": { + "$ref": "#/channels/another-topic" + }, + "title": "another-topic_send", + "description": "Custom, optional description defined in the AsyncPublisher annotation", + "bindings": { + "sns": { + "consumers": [ + { + "protocol": "sqs", + "endpoint": { }, + "filterPolicyScope": "MessageAttributes", + "rawMessageDelivery": true + } + ], + "bindingVersion": "0.1.0" + } + }, + "messages": [ + { + "$ref": "#/channels/another-topic/messages/io.github.stavshamir.springwolf.example.sns.dtos.AnotherPayloadDto" + } + ] + }, + "example-topic_receive_receiveExamplePayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/example-topic" + }, + "title": "example-topic_receive", + "description": "Auto-generated description", + "bindings": { + "sns": { + "consumers": [ + { + "protocol": "sqs", + "endpoint": { }, + "filterPolicyScope": "MessageAttributes", + "rawMessageDelivery": true + } + ], + "bindingVersion": "0.1.0" + } + }, + "messages": [ + { + "$ref": "#/channels/example-topic/messages/io.github.stavshamir.springwolf.example.sns.dtos.ExamplePayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-examples/springwolf-sqs-example/build.gradle b/springwolf-examples/springwolf-sqs-example/build.gradle index 24a21d05b..25c7e50b8 100644 --- a/springwolf-examples/springwolf-sqs-example/build.gradle +++ b/springwolf-examples/springwolf-sqs-example/build.gradle @@ -16,12 +16,14 @@ dependencyManagement { dependencies { implementation project(":springwolf-core") - implementation project(":springwolf-add-ons:springwolf-generic-binding") implementation project(":springwolf-plugins:springwolf-sqs") annotationProcessor project(":springwolf-plugins:springwolf-sqs") runtimeOnly project(":springwolf-ui") + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + runtimeOnly "org.springframework.boot:spring-boot-starter-web" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" diff --git a/springwolf-examples/springwolf-sqs-example/src/main/java/io/github/stavshamir/springwolf/example/sqs/producers/AnotherProducer.java b/springwolf-examples/springwolf-sqs-example/src/main/java/io/github/stavshamir/springwolf/example/sqs/producers/AnotherProducer.java index b810728e3..4ab8f1309 100644 --- a/springwolf-examples/springwolf-sqs-example/src/main/java/io/github/stavshamir/springwolf/example/sqs/producers/AnotherProducer.java +++ b/springwolf-examples/springwolf-sqs-example/src/main/java/io/github/stavshamir/springwolf/example/sqs/producers/AnotherProducer.java @@ -2,10 +2,10 @@ package io.github.stavshamir.springwolf.example.sqs.producers; import io.awspring.cloud.sqs.operations.SqsTemplate; -import io.github.stavshamir.springwolf.addons.generic_binding.annotation.AsyncGenericOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncOperation; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AsyncPublisher; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncQueueBinding; import io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -22,10 +22,7 @@ public class AnotherProducer { @AsyncOperation( channelName = QUEUE, description = "Custom, optional description defined in the AsyncPublisher annotation")) - @SqsAsyncOperationBinding - @AsyncGenericOperationBinding( - type = "sqs", - fields = {"internal-field=customValue", "nested.key=nestedValue"}) + @SqsAsyncOperationBinding(queues = {@SqsAsyncQueueBinding(name = "queue-name")}) public void sendMessage(AnotherPayloadDto msg) { template.send(QUEUE, msg); } diff --git a/springwolf-examples/springwolf-sqs-example/src/main/resources/application.properties b/springwolf-examples/springwolf-sqs-example/src/main/resources/application.properties index bda7b6ee5..9db619460 100644 --- a/springwolf-examples/springwolf-sqs-example/src/main/resources/application.properties +++ b/springwolf-examples/springwolf-sqs-example/src/main/resources/application.properties @@ -26,7 +26,7 @@ springwolf.docket.info.contact.email=example@example.com springwolf.docket.info.contact.url=https://github.com/springwolf/springwolf-core springwolf.docket.info.license.name=Apache License 2.0 springwolf.docket.servers.sqs.protocol=sqs -springwolf.docket.servers.sqs.url=http://localhost:4566 +springwolf.docket.servers.sqs.host=http://localhost:4566 springwolf.use-fqn=true springwolf.plugin.sqs.publishing.enabled=true 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 6fdd4bf9e..16adb93b0 100644 --- a/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json +++ b/springwolf-examples/springwolf-sqs-example/src/test/resources/asyncapi.json @@ -1,5 +1,5 @@ { - "asyncapi": "2.6.0", + "asyncapi": "3.0.0", "info": { "title": "Springwolf example project - SQS", "version": "1.0.0", @@ -17,85 +17,36 @@ "defaultContentType": "application/json", "servers": { "sqs": { - "url": "http://localhost:4566", + "host": "http://localhost:4566", "protocol": "sqs" } }, "channels": { "another-queue": { - "subscribe": { - "operationId": "another-queue_subscribe", - "description": "Custom, optional description defined in the AsyncPublisher annotation", - "bindings": { - "sqs": { - "internal-field": "customValue", - "nested": { - "key": "nestedValue" - } - } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "description": "Another payload model", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sqs": { } - } - } - }, - "publish": { - "operationId": "another-queue_publish_receiveAnotherPayload", - "description": "Auto-generated description", - "bindings": { - "sqs": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto", - "title": "AnotherPayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sqs": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" } } }, "example-queue": { - "publish": { - "operationId": "example-queue_publish_receiveExamplePayload", - "description": "Auto-generated description", - "bindings": { - "sqs": { } - }, - "message": { - "schemaFormat": "application/vnd.oai.openapi+json;version=3.0.0", - "name": "io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto", - "title": "ExamplePayloadDto", - "payload": { - "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto" - }, - "headers": { - "$ref": "#/components/schemas/HeadersNotDocumented" - }, - "bindings": { - "sqs": { } - } + "messages": { + "io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto": { + "$ref": "#/components/messages/io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto" } }, "bindings": { - "sqs": { } + "sqs": { + "queue": { + "name": "example-queue", + "fifoQueue": true, + "deliveryDelay": 0, + "visibilityTimeout": 30, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 345600 + }, + "bindingVersion": "0.2.0" + } } } }, @@ -167,7 +118,122 @@ "someString": "some string value" } } + }, + "messages": { + "io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto", + "title": "AnotherPayloadDto", + "bindings": { + "sqs": { } + } + }, + "io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto": { + "headers": { + "$ref": "#/components/schemas/HeadersNotDocumented" + }, + "payload": { + "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0", + "schema": { + "$ref": "#/components/schemas/io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto" + } + }, + "name": "io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto", + "title": "ExamplePayloadDto", + "bindings": { + "sqs": { } + } + } } }, - "tags": [ ] + "operations": { + "another-queue_receive_receiveAnotherPayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/another-queue" + }, + "bindings": { + "sqs": { + "queues": [ + { + "name": "another-queue", + "fifoQueue": true, + "deliveryDelay": 0, + "visibilityTimeout": 30, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 345600 + } + ], + "bindingVersion": "0.2.0" + } + }, + "messages": [ + { + "$ref": "#/channels/another-queue/messages/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" + } + ] + }, + "another-queue_send_sendMessage": { + "action": "send", + "channel": { + "$ref": "#/channels/another-queue" + }, + "title": "another-queue_send", + "description": "Custom, optional description defined in the AsyncPublisher annotation", + "bindings": { + "sqs": { + "queues": [ + { + "name": "queue-name", + "fifoQueue": true, + "deliveryDelay": 0, + "visibilityTimeout": 30, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 345600 + } + ], + "bindingVersion": "0.2.0" + } + }, + "messages": [ + { + "$ref": "#/channels/another-queue/messages/io.github.stavshamir.springwolf.example.sqs.dtos.AnotherPayloadDto" + } + ] + }, + "example-queue_receive_receiveExamplePayload": { + "action": "receive", + "channel": { + "$ref": "#/channels/example-queue" + }, + "bindings": { + "sqs": { + "queues": [ + { + "name": "example-queue", + "fifoQueue": true, + "deliveryDelay": 0, + "visibilityTimeout": 30, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 345600 + } + ], + "bindingVersion": "0.2.0" + } + }, + "messages": [ + { + "$ref": "#/channels/example-queue/messages/io.github.stavshamir.springwolf.example.sqs.dtos.ExamplePayloadDto" + } + ] + } + } } \ No newline at end of file diff --git a/springwolf-plugins/springwolf-amqp-plugin/README.md b/springwolf-plugins/springwolf-amqp-plugin/README.md index 6d53340bf..eb56d1438 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/README.md +++ b/springwolf-plugins/springwolf-amqp-plugin/README.md @@ -42,7 +42,7 @@ springwolf.docket.info.title=${spring.application.name} springwolf.docket.info.version=1.0.0 springwolf.docket.servers.amqp.protocol=amqp -springwolf.docket.servers.amqp.url=amqp:5672 +springwolf.docket.servers.amqp.host=amqp:5672 ``` The basePackage field must be set with the name of the package containing the classes to be scanned for `@RabbitListener` diff --git a/springwolf-plugins/springwolf-amqp-plugin/build.gradle b/springwolf-plugins/springwolf-amqp-plugin/build.gradle index 952e32c46..0cc0d8513 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/build.gradle +++ b/springwolf-plugins/springwolf-amqp-plugin/build.gradle @@ -8,8 +8,8 @@ plugins { dependencies { api project(":springwolf-core") + api project(":springwolf-asyncapi") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation "org.springframework:spring-context" @@ -24,6 +24,8 @@ dependencies { permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" @@ -32,6 +34,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" testImplementation("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}") testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" + testImplementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" testImplementation "org.springframework.boot:spring-boot-test" testImplementation "org.springframework:spring-beans" diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/amqp/SpringwolfAmqpScannerConfiguration.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/amqp/SpringwolfAmqpScannerConfiguration.java index 03b4c27f8..53a189f23 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/amqp/SpringwolfAmqpScannerConfiguration.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/amqp/SpringwolfAmqpScannerConfiguration.java @@ -7,8 +7,11 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.AmqpOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.ClassLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.ClassLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersForAmqpBuilder; @@ -76,6 +79,30 @@ public SimpleChannelsScanner simpleRabbitClassLevelListenerAnnotationChannelsSca return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_RABBIT_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleRabbitClassLevelListenerAnnotationOperationsScanner( + SpringwolfClassScanner classScanner, + AmqpBindingFactory amqpBindingBuilder, + AsyncHeadersForAmqpBuilder asyncHeadersForAmqpBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + ClassLevelAnnotationOperationsScanner strategy = + new ClassLevelAnnotationOperationsScanner<>( + RabbitListener.class, + RabbitHandler.class, + amqpBindingBuilder, + asyncHeadersForAmqpBuilder, + payloadClassExtractor, + schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @ConditionalOnProperty( name = SPRINGWOLF_SCANNER_RABBIT_LISTENER_ENABLED, @@ -93,6 +120,23 @@ public SimpleChannelsScanner simpleRabbitMethodLevelListenerAnnotationChannelsSc return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_RABBIT_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleRabbitMethodLevelListenerAnnotationOperationsScanner( + SpringwolfClassScanner classScanner, + AmqpBindingFactory amqpBindingBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + MethodLevelAnnotationOperationsScanner strategy = new MethodLevelAnnotationOperationsScanner<>( + RabbitListener.class, amqpBindingBuilder, payloadClassExtractor, schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @Order(value = BindingProcessorPriority.PROTOCOL_BINDING) @ConditionalOnMissingBean diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/AmqpBindingFactory.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/AmqpBindingFactory.java index f42239773..f81b61e8b 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/AmqpBindingFactory.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/AmqpBindingFactory.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.RabbitListenerUtil; +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 org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.core.Queue; @@ -29,17 +29,17 @@ public String getChannelName(RabbitListener annotation) { } @Override - public Map buildChannelBinding(RabbitListener annotation) { + public Map buildChannelBinding(RabbitListener annotation) { return RabbitListenerUtil.buildChannelBinding(annotation, stringValueResolver, context); } @Override - public Map buildOperationBinding(RabbitListener annotation) { + public Map buildOperationBinding(RabbitListener annotation) { return RabbitListenerUtil.buildOperationBinding(annotation, stringValueResolver, context); } @Override - public Map buildMessageBinding(RabbitListener annotation) { + public Map buildMessageBinding(RabbitListener annotation) { return RabbitListenerUtil.buildMessageBinding(); } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessor.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessor.java index 9fb8bac40..43d064d34 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessor.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpMessageBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AmqpAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; @@ -23,8 +23,8 @@ public void setEmbeddedValueResolver(StringValueResolver resolver) { @Override public Optional process(Method method) { return Arrays.stream(method.getAnnotations()) - .filter(annotation -> annotation instanceof AmqpAsyncOperationBinding) - .map(annotation -> (AmqpAsyncOperationBinding) annotation) + .filter(AmqpAsyncOperationBinding.class::isInstance) + .map(AmqpAsyncOperationBinding.class::cast) .findAny() .map(this::mapToMessageBinding); } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessor.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessor.java index 9aaf5b0e5..d3140c220 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessor.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/AmqpOperationBindingProcessor.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.AmqpAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import java.util.Arrays; diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java index e1979f612..b6e833f68 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/RabbitListenerUtil.java @@ -1,12 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; +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.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelExchangeProperties; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelExchangeType; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelQueueProperties; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; @@ -81,44 +85,45 @@ private static Stream streamQueueNames(RabbitListener rabbitListenerAnno Arrays.stream(rabbitListenerAnnotation.queuesToDeclare()).map(Queue::name)); } - public static Map buildChannelBinding( + public static Map buildChannelBinding( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { AMQPChannelBinding.AMQPChannelBindingBuilder channelBinding = AMQPChannelBinding.builder(); - channelBinding.queue(buildQueueProperties(annotation, resolver, context)); - String exchangeName = getExchangeName(annotation, resolver, context); - channelBinding.exchange(buildExchangeProperties(annotation, exchangeName, context)); if (exchangeName.isEmpty()) { - channelBinding.is("queue"); + channelBinding.is(AMQPChannelType.QUEUE); + channelBinding.queue(buildQueueProperties(annotation, resolver, context)); } else { - channelBinding.is("routingKey"); + channelBinding.is(AMQPChannelType.ROUTING_KEY); + channelBinding.exchange(buildExchangeProperties(annotation, exchangeName, context)); } return Map.of("amqp", channelBinding.build()); } - private static AMQPChannelBinding.ExchangeProperties buildExchangeProperties( + private static AMQPChannelExchangeProperties buildExchangeProperties( RabbitListener annotation, String exchangeName, RabbitListenerUtilContext context) { // 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); if (exchange != null) { - return AMQPChannelBinding.ExchangeProperties.builder() + return AMQPChannelExchangeProperties.builder() .name(exchangeName) - .type(exchange.getType()) + .type(AMQPChannelExchangeType.fromString(exchange.getType())) .durable(exchange.isDurable()) .autoDelete(exchange.isAutoDelete()) .build(); } - return AMQPChannelBinding.ExchangeProperties.builder() + var type = Stream.of(annotation.bindings()) + .map(it -> it.exchange().type()) + .findFirst() + .orElse(DEFAULT_EXCHANGE_TYPE); + + return AMQPChannelExchangeProperties.builder() .name(exchangeName) - .type(Stream.of(annotation.bindings()) - .map(it -> it.exchange().type()) - .findFirst() - .orElse(DEFAULT_EXCHANGE_TYPE)) + .type(AMQPChannelExchangeType.fromString(type)) .durable(Boolean.valueOf(Stream.of(annotation.bindings()) .map(it -> it.exchange().durable()) .findFirst() @@ -130,7 +135,7 @@ private static AMQPChannelBinding.ExchangeProperties buildExchangeProperties( .build(); } - private static AMQPChannelBinding.QueueProperties buildQueueProperties( + 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); @@ -142,7 +147,7 @@ private static AMQPChannelBinding.QueueProperties buildQueueProperties( Arrays.stream(annotation.bindings()).map(QueueBinding::value).findFirst(); if (queueOpt.isPresent()) { Queue queueAnnotation = queueOpt.get(); - return AMQPChannelBinding.QueueProperties.builder() + return AMQPChannelQueueProperties.builder() .name(resolver.resolveStringValue(queueAnnotation.name())) .autoDelete(parse(queueAnnotation.autoDelete(), autoDelete)) .durable(parse(queueAnnotation.durable(), durable)) @@ -150,7 +155,7 @@ private static AMQPChannelBinding.QueueProperties buildQueueProperties( .build(); } - return AMQPChannelBinding.QueueProperties.builder() + return AMQPChannelQueueProperties.builder() .name(queueName) .autoDelete(autoDelete) .durable(durable) @@ -186,7 +191,7 @@ private static String getExchangeName( return exchangeName; } - public static Map buildOperationBinding( + public static Map buildOperationBinding( RabbitListener annotation, StringValueResolver resolver, RabbitListenerUtilContext context) { return Map.of( "amqp", @@ -227,7 +232,7 @@ private static List getRoutingKeys( return routingKeys; } - public static Map buildMessageBinding() { + public static Map buildMessageBinding() { // currently the feature to define amqp message binding is not implemented. return Map.of("amqp", new AMQPMessageBinding()); } diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java index 084d6de4f..c3b4ba09d 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/AmqpAsyncOperationBinding.java @@ -27,7 +27,7 @@ int priority() default 0; - int deliveryMode() default 0; + int deliveryMode() default 1; boolean mandatory() default false; diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpConsumerData.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpConsumerData.java index 31ed7fd89..4cbc35797 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpConsumerData.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpConsumerData.java @@ -1,10 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelExchangeProperties; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import lombok.Builder; import java.util.Collections; @@ -24,12 +26,12 @@ public AmqpConsumerData( this.channelName = queueName; this.description = description; - AMQPChannelBinding.ExchangeProperties exchangeProperties = new AMQPChannelBinding.ExchangeProperties(); + AMQPChannelExchangeProperties exchangeProperties = new AMQPChannelExchangeProperties(); exchangeProperties.setName(exchangeName); this.channelBinding = Map.of( "amqp", AMQPChannelBinding.builder() - .is("routingKey") + .is(AMQPChannelType.ROUTING_KEY) .exchange(exchangeProperties) .build()); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpProducerData.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpProducerData.java index 119da3fe3..e11054c99 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpProducerData.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/AmqpProducerData.java @@ -1,10 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.message.amqp.AMQPMessageBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelExchangeProperties; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelType; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; import lombok.Builder; import java.util.Collections; @@ -24,12 +26,12 @@ public AmqpProducerData( this.channelName = queueName; this.description = description; - AMQPChannelBinding.ExchangeProperties exchangeProperties = new AMQPChannelBinding.ExchangeProperties(); + AMQPChannelExchangeProperties exchangeProperties = new AMQPChannelExchangeProperties(); exchangeProperties.setName(exchangeName); this.channelBinding = Map.of( "amqp", AMQPChannelBinding.builder() - .is("routingKey") + .is(AMQPChannelType.ROUTING_KEY) .exchange(exchangeProperties) .build()); diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducer.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducer.java index 7285c6569..0cc6dafe3 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducer.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducer.java @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.producer; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.AsyncApiService; import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.util.CollectionUtils; @@ -31,10 +31,15 @@ public SpringwolfAmqpProducer(AsyncApiService asyncApiService, List clazz) { assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); assertEquals( AMQPChannelBinding.builder() - .is("queue") - .exchange(AMQPChannelBinding.ExchangeProperties.builder() - .name("") - .type(ExchangeTypes.DIRECT) - .durable(true) - .autoDelete(false) - .vhost("/") - .build()) - .queue(AMQPChannelBinding.QueueProperties.builder() + .is(AMQPChannelType.QUEUE) + .queue(AMQPChannelQueueProperties.builder() .name("queue-1") .durable(true) .autoDelete(false) @@ -99,7 +95,7 @@ void buildOperationBinding(Class clazz) { when(resolver.resolveStringValue("${queue-1}")).thenReturn("queue-1"); // when - Map operationBinding = + Map operationBinding = RabbitListenerUtil.buildOperationBinding(annotation, resolver, emptyContext); // then @@ -111,7 +107,7 @@ void buildOperationBinding(Class clazz) { @Test void buildMessageBinding() { // when - Map messageBinding = RabbitListenerUtil.buildMessageBinding(); + Map messageBinding = RabbitListenerUtil.buildMessageBinding(); // then assertEquals(1, messageBinding.size()); @@ -167,20 +163,13 @@ void buildChannelBinding() { assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); assertEquals( AMQPChannelBinding.builder() - .is("routingKey") - .exchange(AMQPChannelBinding.ExchangeProperties.builder() + .is(AMQPChannelType.ROUTING_KEY) + .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(ExchangeTypes.TOPIC) + .type(AMQPChannelExchangeType.TOPIC) .durable(true) .autoDelete(false) .build()) - .queue(AMQPChannelBinding.QueueProperties.builder() - .name("queue-1") - .durable(true) - .autoDelete(false) - .exclusive(false) - .vhost("/") - .build()) .build(), channelBinding.get("amqp")); } @@ -257,20 +246,13 @@ void buildChannelBinding() { assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); assertEquals( AMQPChannelBinding.builder() - .is("routingKey") - .exchange(AMQPChannelBinding.ExchangeProperties.builder() + .is(AMQPChannelType.ROUTING_KEY) + .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(ExchangeTypes.DIRECT) + .type(AMQPChannelExchangeType.DIRECT) .durable(true) .autoDelete(false) .build()) - .queue(AMQPChannelBinding.QueueProperties.builder() - .name("queue-1") - .durable(true) - .autoDelete(false) - .exclusive(false) - .vhost("/") - .build()) .build(), channelBinding.get("amqp")); } @@ -364,20 +346,13 @@ void buildChannelBinding() { assertEquals(Sets.newTreeSet("amqp"), channelBinding.keySet()); assertEquals( AMQPChannelBinding.builder() - .is("routingKey") - .exchange(AMQPChannelBinding.ExchangeProperties.builder() + .is(AMQPChannelType.ROUTING_KEY) + .exchange(AMQPChannelExchangeProperties.builder() .name("exchange-name") - .type(ExchangeTypes.TOPIC) + .type(AMQPChannelExchangeType.TOPIC) .durable(false) .autoDelete(true) .build()) - .queue(AMQPChannelBinding.QueueProperties.builder() - .name("queue-1") - .durable(false) - .autoDelete(true) - .exclusive(true) - .vhost("/") - .build()) .build(), channelBinding.get("amqp")); } @@ -426,7 +401,7 @@ private void methodWithAnnotation(String payload) {} } @Nested - public class RabbitListenerUtilContextTest { + class RabbitListenerUtilContextTest { @Test void testEmptyContext() { // when diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java index a514af5ef..3652e1472 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfAmqpProducerConfigurationIntegrationTest.java @@ -40,7 +40,7 @@ public class SpringwolfAmqpProducerConfigurationIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.amqp.publishing.enabled=true" }) @MockBeans( @@ -81,7 +81,7 @@ void springwolfAmqpProducerShouldBePresentInSpringContext() { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.amqp.publishing.enabled=false" }) @MockBeans( diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducerTest.java b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducerTest.java index 9f8eb736f..ff7271610 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducerTest.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/test/java/io/github/stavshamir/springwolf/producer/SpringwolfAmqpProducerTest.java @@ -1,19 +1,22 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.producer; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.info.Info; -import com.asyncapi.v2.binding.channel.amqp.AMQPChannelBinding; -import com.asyncapi.v2.binding.operation.amqp.AMQPOperationBinding; import io.github.stavshamir.springwolf.asyncapi.AsyncApiService; import io.github.stavshamir.springwolf.asyncapi.types.AsyncAPI; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPChannelExchangeProperties; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.amqp.AMQPOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.ChannelReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.info.Info; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.mockito.ArgumentMatchers.eq; @@ -33,14 +36,14 @@ void setUp() { asyncApiService = mock(AsyncApiService.class); rabbitTemplate = mock(RabbitTemplate.class); - springwolfAmqpProducer = new SpringwolfAmqpProducer(asyncApiService, Collections.singletonList(rabbitTemplate)); + springwolfAmqpProducer = new SpringwolfAmqpProducer(asyncApiService, List.of(rabbitTemplate)); } @Test void send_defaultExchangeAndChannelNameAsRoutingKey() { AsyncAPI asyncAPI = AsyncAPI.builder() .info(new Info()) - .channels(Map.of("channel-name", new ChannelItem())) + .channels(Map.of("channel-name", new ChannelObject())) .build(); when(asyncApiService.getAsyncAPI()).thenReturn(asyncAPI); @@ -52,20 +55,26 @@ void send_defaultExchangeAndChannelNameAsRoutingKey() { @Test void send_exchangeAndNoRoutingKey() { - AMQPChannelBinding.ExchangeProperties properties = new AMQPChannelBinding.ExchangeProperties(); + AMQPChannelExchangeProperties properties = new AMQPChannelExchangeProperties(); properties.setName("exchange-name"); - ChannelItem channelItem = ChannelItem.builder() + ChannelObject channelItem = ChannelObject.builder() .bindings(Map.of( "amqp", AMQPChannelBinding.builder().exchange(properties).build())) - .publish(Operation.builder() - .bindings(Map.of("amqp", new AMQPOperationBinding())) - .build()) .build(); - Map channels = Map.of("channel-name", channelItem); + Map channels = Map.of("channel-name", channelItem); + Operation operation = Operation.builder() + .action(OperationAction.SEND) + .bindings(Map.of("amqp", AMQPOperationBinding.builder().build())) + .channel(ChannelReference.fromChannel("channel-name")) + .build(); + Map operations = Map.of("amqp", operation); - AsyncAPI asyncAPI = - AsyncAPI.builder().info(new Info()).channels(channels).build(); + AsyncAPI asyncAPI = AsyncAPI.builder() + .info(new Info()) + .channels(channels) + .operations(operations) + .build(); when(asyncApiService.getAsyncAPI()).thenReturn(asyncAPI); Map payload = new HashMap<>(); @@ -76,24 +85,28 @@ void send_exchangeAndNoRoutingKey() { @Test void send_exchangeAndRoutingKeyFromBindings() { - AMQPChannelBinding.ExchangeProperties properties = new AMQPChannelBinding.ExchangeProperties(); + AMQPChannelExchangeProperties properties = new AMQPChannelExchangeProperties(); properties.setName("exchange-name"); - ChannelItem channelItem = ChannelItem.builder() + ChannelObject channelItem = ChannelObject.builder() .bindings(Map.of( "amqp", AMQPChannelBinding.builder().exchange(properties).build())) - .publish(Operation.builder() - .bindings(Map.of( - "amqp", - AMQPOperationBinding.builder() - .cc(Collections.singletonList("routing-key")) - .build())) - .build()) .build(); - Map channels = Map.of("channel-name", channelItem); + Map channels = Map.of("channel-name", channelItem); + Operation operation = Operation.builder() + .bindings(Map.of( + "amqp", + AMQPOperationBinding.builder() + .cc(List.of("routing-key")) + .build())) + .build(); + Map operations = Map.of("amqp", operation); - AsyncAPI asyncAPI = - AsyncAPI.builder().info(new Info()).channels(channels).build(); + AsyncAPI asyncAPI = AsyncAPI.builder() + .info(new Info()) + .channels(channels) + .operations(operations) + .build(); when(asyncApiService.getAsyncAPI()).thenReturn(asyncAPI); Map payload = new HashMap<>(); diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/build.gradle b/springwolf-plugins/springwolf-cloud-stream-plugin/build.gradle index f214c38a6..a36e98644 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/build.gradle +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/build.gradle @@ -17,16 +17,16 @@ dependencyManagement { } dependencies { + api project(":springwolf-asyncapi") api project(":springwolf-core") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" - implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation "org.springframework:spring-context" implementation "org.springframework.cloud:spring-cloud-stream" implementation "org.springframework.boot:spring-boot-autoconfigure" + compileOnly "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/cloudstream/SpringwolfCloudStreamAutoConfiguration.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/cloudstream/SpringwolfCloudStreamAutoConfiguration.java index 739b38e64..0acd380e1 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/cloudstream/SpringwolfCloudStreamAutoConfiguration.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/cloudstream/SpringwolfCloudStreamAutoConfiguration.java @@ -3,6 +3,7 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.beans.BeanMethodsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream.CloudStreamFunctionChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream.CloudStreamFunctionOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream.FunctionalChannelBeanBuilder; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; @@ -35,6 +36,21 @@ public CloudStreamFunctionChannelsScanner cloudStreamFunctionChannelsScanner( functionalChannelBeanBuilder); } + @Bean + public CloudStreamFunctionOperationsScanner cloudStreamFunctionOperationsScanner( + AsyncApiDocketService asyncApiDocketService, + BeanMethodsScanner beanMethodsScanner, + SchemasService schemasService, + BindingServiceProperties cloudstreamBindingServiceProperties, + FunctionalChannelBeanBuilder functionalChannelBeanBuilder) { + return new CloudStreamFunctionOperationsScanner( + asyncApiDocketService, + beanMethodsScanner, + schemasService, + cloudstreamBindingServiceProperties, + functionalChannelBeanBuilder); + } + @Bean public FunctionalChannelBeanBuilder functionalChannelBeanBuilder(PayloadClassExtractor payloadClassExtractor) { return new FunctionalChannelBeanBuilder(payloadClassExtractor); diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScanner.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScanner.java index acd309513..896329839 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScanner.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScanner.java @@ -1,20 +1,22 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; -import com.asyncapi.v2._6_0.model.server.Server; -import com.asyncapi.v2.binding.message.MessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.beans.BeanMethodsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelMerger; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelsScanner; import io.github.stavshamir.springwolf.asyncapi.types.channel.bindings.EmptyChannelBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.bindings.EmptyOperationBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.bindings.EmptyMessageBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +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.model.channel.ChannelObject; +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.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.asyncapi.v3.model.server.Server; import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -25,7 +27,6 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor @@ -38,76 +39,62 @@ public class CloudStreamFunctionChannelsScanner implements ChannelsScanner { private final FunctionalChannelBeanBuilder functionalChannelBeanBuilder; @Override - public Map scan() { + public Map scan() { Set beanMethods = beanMethodsScanner.getBeanMethods(); - return ChannelMerger.merge(beanMethods.stream() + return ChannelMerger.mergeChannels(beanMethods.stream() .map(functionalChannelBeanBuilder::fromMethodBean) .flatMap(Set::stream) .filter(this::isChannelBean) .map(this::toChannelEntry) - .collect(Collectors.toList())); + .toList()); } private boolean isChannelBean(FunctionalChannelBeanData beanData) { return cloudStreamBindingsProperties.getBindings().containsKey(beanData.cloudStreamBinding()); } - private Map.Entry toChannelEntry(FunctionalChannelBeanData beanData) { + private Map.Entry toChannelEntry(FunctionalChannelBeanData beanData) { String channelName = cloudStreamBindingsProperties .getBindings() .get(beanData.cloudStreamBinding()) .getDestination(); - String operationId = buildOperationId(beanData, channelName); - ChannelItem channelItem = buildChannel(beanData, operationId); + ChannelObject channelItem = buildChannel(beanData); return Map.entry(channelName, channelItem); } - private ChannelItem buildChannel(FunctionalChannelBeanData beanData, String operationId) { + private ChannelObject buildChannel(FunctionalChannelBeanData beanData) { Class payloadType = beanData.payloadType(); - String modelName = schemasService.register(payloadType); - String headerModelName = schemasService.register(AsyncHeaders.NOT_DOCUMENTED); + String modelName = schemasService.registerSchema(payloadType); + String headerModelName = schemasService.registerSchema(AsyncHeaders.NOT_DOCUMENTED); - Message message = Message.builder() + var messagePayload = MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(modelName)) + .build()); + + MessageObject message = MessageObject.builder() .name(payloadType.getName()) .title(modelName) - .description(null) - .payload(PayloadReference.fromModelName(modelName)) - .headers(HeaderReference.fromModelName(headerModelName)) + .payload(messagePayload) + .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) .bindings(buildMessageBinding()) .build(); + this.schemasService.registerMessage(message); - Operation operation = Operation.builder() - .description("Auto-generated description") - .operationId(operationId) - .message(message) - .bindings(buildOperationBinding()) + Map channelBinding = buildChannelBinding(); + return ChannelObject.builder() + .bindings(channelBinding) + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) .build(); - - Map channelBinding = buildChannelBinding(); - return beanData.beanType() == FunctionalChannelBeanData.BeanType.CONSUMER - ? ChannelItem.builder() - .bindings(channelBinding) - .publish(operation) - .build() - : ChannelItem.builder() - .bindings(channelBinding) - .subscribe(operation) - .build(); } - private Map buildMessageBinding() { + private Map buildMessageBinding() { String protocolName = getProtocolName(); return Map.of(protocolName, new EmptyMessageBinding()); } - private Map buildOperationBinding() { - String protocolName = getProtocolName(); - return Map.of(protocolName, new EmptyOperationBinding()); - } - - private Map buildChannelBinding() { + private Map buildChannelBinding() { String protocolName = getProtocolName(); return Map.of(protocolName, new EmptyChannelBinding()); } @@ -126,11 +113,4 @@ private String getProtocolName() { .orElseThrow(() -> new IllegalStateException("There must be at least one server define in the AsyncApiDocker")); } - - private String buildOperationId(FunctionalChannelBeanData beanData, String channelName) { - String operationName = - beanData.beanType() == FunctionalChannelBeanData.BeanType.CONSUMER ? "publish" : "subscribe"; - - return String.format("%s_%s_%s", channelName, operationName, beanData.beanName()); - } } diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionOperationsScanner.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionOperationsScanner.java new file mode 100644 index 000000000..fd7469d01 --- /dev/null +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionOperationsScanner.java @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream; + +import io.github.stavshamir.springwolf.asyncapi.scanners.beans.BeanMethodsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationMerger; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.OperationsScanner; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.bindings.EmptyOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.bindings.EmptyMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +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.ChannelReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +import io.github.stavshamir.springwolf.asyncapi.v3.model.server.Server; +import io.github.stavshamir.springwolf.configuration.AsyncApiDocket; +import io.github.stavshamir.springwolf.configuration.AsyncApiDocketService; +import io.github.stavshamir.springwolf.schemas.SchemasService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.stream.config.BindingServiceProperties; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Slf4j +@RequiredArgsConstructor +public class CloudStreamFunctionOperationsScanner implements OperationsScanner { + + private final AsyncApiDocketService asyncApiDocketService; + private final BeanMethodsScanner beanMethodsScanner; + private final SchemasService schemasService; + private final BindingServiceProperties cloudStreamBindingsProperties; + private final FunctionalChannelBeanBuilder functionalChannelBeanBuilder; + + @Override + public Map scan() { + Set beanMethods = beanMethodsScanner.getBeanMethods(); + return OperationMerger.mergeOperations(beanMethods.stream() + .map(functionalChannelBeanBuilder::fromMethodBean) + .flatMap(Set::stream) + .filter(this::isChannelBean) + .map(this::toOperationEntry) + .toList()); + } + + private boolean isChannelBean(FunctionalChannelBeanData beanData) { + return cloudStreamBindingsProperties.getBindings().containsKey(beanData.cloudStreamBinding()); + } + + private Map.Entry toOperationEntry(FunctionalChannelBeanData beanData) { + String channelName = cloudStreamBindingsProperties + .getBindings() + .get(beanData.cloudStreamBinding()) + .getDestination(); + + String operationId = buildOperationId(beanData, channelName); + Operation operation = buildOperation(beanData, channelName); + + return Map.entry(operationId, operation); + } + + private Operation buildOperation(FunctionalChannelBeanData beanData, String channelName) { + Class payloadType = beanData.payloadType(); + String modelName = schemasService.registerSchema(payloadType); + String headerModelName = schemasService.registerSchema(AsyncHeaders.NOT_DOCUMENTED); + + MessageObject message = MessageObject.builder() + .name(payloadType.getName()) + .title(modelName) + .payload(MessagePayload.of(MessageReference.toSchema(modelName))) + .headers(MessageHeaders.of(MessageReference.toSchema(headerModelName))) + .bindings(buildMessageBinding()) + .build(); + + var builder = Operation.builder() + .description("Auto-generated description") + .channel(ChannelReference.fromChannel(channelName)) + .messages(List.of(MessageReference.toChannelMessage(channelName, message))) + .bindings(buildOperationBinding()); + if (beanData.beanType() == FunctionalChannelBeanData.BeanType.CONSUMER) { + builder.action(OperationAction.RECEIVE); + } else { + builder.action(OperationAction.SEND); + } + + return builder.build(); + } + + private Map buildMessageBinding() { + String protocolName = getProtocolName(); + return Map.of(protocolName, new EmptyMessageBinding()); + } + + private Map buildOperationBinding() { + String protocolName = getProtocolName(); + return Map.of(protocolName, new EmptyOperationBinding()); + } + + private String getProtocolName() { + AsyncApiDocket docket = asyncApiDocketService.getAsyncApiDocket(); + if (docket.getServers().size() > 1) { + log.warn( + "More than one server has been defined - the channels protocol will be determined by the first one"); + } + + return docket.getServers().entrySet().stream() + .findFirst() + .map(Map.Entry::getValue) + .map(Server::getProtocol) + .orElseThrow(() -> + new IllegalStateException("There must be at least one server define in the AsyncApiDocker")); + } + + private String buildOperationId(FunctionalChannelBeanData beanData, String channelName) { + String operationName = + beanData.beanType() == FunctionalChannelBeanData.BeanType.CONSUMER ? "publish" : "subscribe"; + + return String.format("%s_%s_%s", channelName, operationName, beanData.beanName()); + } +} diff --git a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScannerIntegrationTest.java b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScannerIntegrationTest.java index 796546ecb..94af68b36 100644 --- a/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScannerIntegrationTest.java +++ b/springwolf-plugins/springwolf-cloud-stream-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/cloudstream/CloudStreamFunctionChannelsScannerIntegrationTest.java @@ -1,22 +1,30 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream; -import com.asyncapi.v2._6_0.model.channel.ChannelItem; -import com.asyncapi.v2._6_0.model.channel.operation.Operation; import io.github.stavshamir.springwolf.asyncapi.scanners.beans.DefaultBeanMethodsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ComponentClassScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.ConfigurationClassScanner; import io.github.stavshamir.springwolf.asyncapi.types.channel.bindings.EmptyChannelBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.bindings.EmptyOperationBinding; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.Message; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.PayloadReference; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.bindings.EmptyMessageBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; -import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.HeaderReference; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding; +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.ChannelReference; +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.channel.message.MessagePayload; +import io.github.stavshamir.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.Operation; +import io.github.stavshamir.springwolf.asyncapi.v3.model.operation.OperationAction; +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.configuration.DefaultAsyncApiDocketService; import io.github.stavshamir.springwolf.configuration.properties.SpringwolfConfigProperties; import io.github.stavshamir.springwolf.schemas.DefaultSchemasService; +import io.github.stavshamir.springwolf.schemas.SchemasService; import io.github.stavshamir.springwolf.schemas.example.ExampleJsonGenerator; import org.apache.kafka.streams.kstream.KStream; import org.junit.jupiter.api.Test; @@ -34,6 +42,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -53,6 +62,7 @@ ExampleJsonGenerator.class, DefaultAsyncApiDocketService.class, CloudStreamFunctionChannelsScanner.class, + CloudStreamFunctionOperationsScanner.class, FunctionalChannelBeanBuilder.class, SpringwolfConfigProperties.class }) @@ -63,7 +73,7 @@ "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.asyncapi.scanners.channels.cloudstream", "springwolf.docket.servers.kafka.protocol=kafka", - "springwolf.docket.servers.kafka.url=kafka:9092", + "springwolf.docket.servers.kafka.host=kafka:9092", }) @EnableConfigurationProperties @Import(CloudStreamFunctionChannelsScannerIntegrationTest.Configuration.class) @@ -73,16 +83,22 @@ class CloudStreamFunctionChannelsScannerIntegrationTest { private BindingServiceProperties bindingServiceProperties; @Autowired - private CloudStreamFunctionChannelsScanner scanner; + private CloudStreamFunctionChannelsScanner channelsScanner; + + @Autowired + private CloudStreamFunctionOperationsScanner operationsScanner; + + @Autowired + private SchemasService schemasService; private Map messageBinding = Map.of("kafka", new EmptyMessageBinding()); - private Map operationBinding = Map.of("kafka", new EmptyOperationBinding()); - private Map channelBinding = Map.of("kafka", new EmptyChannelBinding()); + private Map operationBinding = Map.of("kafka", new EmptyOperationBinding()); + private Map channelBinding = Map.of("kafka", new EmptyChannelBinding()); @Test void testNoBindings() { when(bindingServiceProperties.getBindings()).thenReturn(Collections.emptyMap()); - Map channels = scanner.scan(); + Map channels = channelsScanner.scan(); assertThat(channels).isEmpty(); } @@ -95,30 +111,37 @@ void testConsumerBinding() { when(bindingServiceProperties.getBindings()).thenReturn(Map.of("testConsumer-in-0", testConsumerInBinding)); // When scan is called - Map channels = scanner.scan(); + Map actualChannels = channelsScanner.scan(); + Map actualOperations = operationsScanner.scan(); // Then the returned channels contain a ChannelItem with the correct data - Message message = Message.builder() + MessageObject message = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); - Operation operation = Operation.builder() - .bindings(operationBinding) - .description("Auto-generated description") - .operationId("test-consumer-input-topic_publish_testConsumer") - .message(message) + ChannelObject expectedChannel = ChannelObject.builder() + .bindings(channelBinding) + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) .build(); - ChannelItem expectedChannel = ChannelItem.builder() - .bindings(channelBinding) - .publish(operation) + Operation expectedOperation = Operation.builder() + .action(OperationAction.RECEIVE) + .bindings(operationBinding) + .description("Auto-generated description") + .channel(ChannelReference.fromChannel(topicName)) + .messages(List.of(MessageReference.toChannelMessage(topicName, message))) .build(); - assertThat(channels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualChannels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualOperations) + .containsExactly(Map.entry("test-consumer-input-topic_publish_testConsumer", expectedOperation)); + assertThat(schemasService.getMessages()).contains(Map.entry(String.class.getName(), message)); } @Test @@ -130,31 +153,39 @@ void testSupplierBinding() { when(bindingServiceProperties.getBindings()).thenReturn(Map.of("testSupplier-out-0", testSupplierOutBinding)); // When scan is called - Map channels = scanner.scan(); + Map actualChannels = channelsScanner.scan(); + Map actualOperations = operationsScanner.scan(); // Then the returned channels contain a ChannelItem with the correct data - Message message = Message.builder() + MessageObject message = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); - Operation operation = Operation.builder() + Operation expectedOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-supplier-output-topic_subscribe_testSupplier") - .message(message) + .channel(ChannelReference.fromChannel(topicName)) + .messages(List.of(MessageReference.toChannelMessage(topicName, message))) .build(); - ChannelItem expectedChannel = ChannelItem.builder() + ChannelObject expectedChannel = ChannelObject.builder() .bindings(channelBinding) - .subscribe(operation) + .messages(Map.of(message.getName(), MessageReference.toComponentMessage(message))) .build(); - assertThat(channels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualChannels).containsExactly(Map.entry(topicName, expectedChannel)); + assertThat(actualOperations) + .containsExactly(Map.entry("test-supplier-output-topic_subscribe_testSupplier", expectedOperation)); + assertThat(schemasService.getMessages()).contains(Map.entry(String.class.getName(), message)); } @Test @@ -174,51 +205,64 @@ void testFunctionBinding() { "testFunction-out-0", testFunctionOutBinding)); // When scan is called - Map channels = scanner.scan(); + Map actualChannels = channelsScanner.scan(); + Map actualOperations = operationsScanner.scan(); // Then the returned channels contain a publish ChannelItem and a subscribe ChannelItem - Message subscribeMessage = Message.builder() + MessageObject subscribeMessage = MessageObject.builder() .name(Integer.class.getName()) .title(Integer.class.getSimpleName()) - .payload(PayloadReference.fromModelName(Integer.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(Integer.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); Operation subscribeOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-out-topic_subscribe_testFunction") - .message(subscribeMessage) + .channel(ChannelReference.fromChannel(outputTopicName)) + .messages(List.of(MessageReference.toChannelMessage(outputTopicName, subscribeMessage))) .build(); - ChannelItem subscribeChannel = ChannelItem.builder() + ChannelObject subscribeChannel = ChannelObject.builder() .bindings(channelBinding) - .subscribe(subscribeOperation) + .messages(Map.of(subscribeMessage.getName(), MessageReference.toComponentMessage(subscribeMessage))) .build(); - Message publishMessage = Message.builder() + MessageObject publishMessage = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); Operation publishOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-in-topic_publish_testFunction") - .message(publishMessage) + .channel(ChannelReference.fromChannel(inputTopicName)) + .messages(List.of(MessageReference.toChannelMessage(inputTopicName, publishMessage))) .build(); - ChannelItem publishChannel = ChannelItem.builder() + ChannelObject publishChannel = ChannelObject.builder() .bindings(channelBinding) - .publish(publishOperation) + .messages(Map.of(publishMessage.getName(), MessageReference.toComponentMessage(publishMessage))) .build(); - assertThat(channels) + assertThat(actualChannels) .contains(Map.entry(inputTopicName, publishChannel), Map.entry(outputTopicName, subscribeChannel)); + assertThat(actualOperations) + .contains( + Map.entry("test-in-topic_publish_testFunction", publishOperation), + Map.entry("test-out-topic_subscribe_testFunction", subscribeOperation)); } @Test @@ -238,51 +282,66 @@ void testKStreamFunctionBinding() { "kStreamTestFunction-out-0", testFunctionOutBinding)); // When scan is called - Map channels = scanner.scan(); + Map actualChannels = channelsScanner.scan(); + Map actualOperations = operationsScanner.scan(); // Then the returned channels contain a publish ChannelItem and a subscribe ChannelItem - Message subscribeMessage = Message.builder() + MessageObject subscribeMessage = MessageObject.builder() .name(Integer.class.getName()) .title(Integer.class.getSimpleName()) - .payload(PayloadReference.fromModelName(Integer.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(Integer.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); Operation subscribeOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-out-topic_subscribe_kStreamTestFunction") - .message(subscribeMessage) + .channel(ChannelReference.fromChannel(outputTopicName)) + .messages(List.of(MessageReference.toChannelMessage(outputTopicName, subscribeMessage))) .build(); - ChannelItem subscribeChannel = ChannelItem.builder() + ChannelObject subscribeChannel = ChannelObject.builder() .bindings(channelBinding) - .subscribe(subscribeOperation) + .messages(Map.of(subscribeMessage.getName(), MessageReference.toComponentMessage(subscribeMessage))) .build(); - Message publishMessage = Message.builder() + MessageObject publishMessage = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); Operation publishOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-in-topic_publish_kStreamTestFunction") - .message(publishMessage) + .channel(ChannelReference.fromChannel(inputTopicName)) + .messages(List.of(MessageReference.toChannelMessage(inputTopicName, publishMessage))) .build(); - ChannelItem publishChannel = ChannelItem.builder() + ChannelObject publishChannel = ChannelObject.builder() .bindings(channelBinding) - .publish(publishOperation) + .messages(Map.of(publishMessage.getName(), MessageReference.toComponentMessage(publishMessage))) .build(); - assertThat(channels) + assertThat(actualChannels) .contains(Map.entry(inputTopicName, publishChannel), Map.entry(outputTopicName, subscribeChannel)); + assertThat(actualOperations) + .contains( + Map.entry("test-in-topic_publish_kStreamTestFunction", publishOperation), + Map.entry("test-out-topic_subscribe_kStreamTestFunction", subscribeOperation)); + assertThat(schemasService.getMessages()).contains(Map.entry(String.class.getName(), publishMessage)); + assertThat(schemasService.getMessages()).contains(Map.entry(Integer.class.getName(), subscribeMessage)); } @Test @@ -301,46 +360,65 @@ void testFunctionBindingWithSameTopicName() { "testFunction-out-0", testFunctionOutBinding)); // When scan is called - Map channels = scanner.scan(); + Map actualChannels = channelsScanner.scan(); + Map actualOperations = operationsScanner.scan(); // Then the returned merged channels contain a publish operation and a subscribe operation - Message subscribeMessage = Message.builder() + MessageObject subscribeMessage = MessageObject.builder() .name(Integer.class.getName()) .title(Integer.class.getSimpleName()) - .payload(PayloadReference.fromModelName(Integer.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(Integer.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); Operation subscribeOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.SEND) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-topic_subscribe_testFunction") - .message(subscribeMessage) + .channel(ChannelReference.fromChannel(topicName)) + .messages(List.of(MessageReference.toChannelMessage(topicName, subscribeMessage))) .build(); - Message publishMessage = Message.builder() + MessageObject publishMessage = MessageObject.builder() .name(String.class.getName()) .title(String.class.getSimpleName()) - .payload(PayloadReference.fromModelName(String.class.getSimpleName())) - .headers(HeaderReference.fromModelName(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())) - .bindings(messageBinding) + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(SchemaReference.fromSchema(String.class.getSimpleName())) + .build())) + .headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName()))) + .bindings(Map.of("kafka", new EmptyMessageBinding())) .build(); + // "test-topic_publish_testFunction" Operation publishOperation = Operation.builder() + .bindings(operationBinding) + .action(OperationAction.RECEIVE) .bindings(operationBinding) .description("Auto-generated description") - .operationId("test-topic_publish_testFunction") - .message(publishMessage) + .channel(ChannelReference.fromChannel(topicName)) + .messages(List.of(MessageReference.toChannelMessage(topicName, publishMessage))) .build(); - ChannelItem mergedChannel = ChannelItem.builder() + ChannelObject mergedChannel = ChannelObject.builder() .bindings(channelBinding) - .publish(publishOperation) - .subscribe(subscribeOperation) + .messages(Map.of( + publishMessage.getName(), + MessageReference.toComponentMessage(publishMessage), + subscribeMessage.getName(), + MessageReference.toComponentMessage(subscribeMessage))) .build(); - assertThat(channels).contains(Map.entry(topicName, mergedChannel)); + assertThat(actualChannels).contains(Map.entry(topicName, mergedChannel)); + assertThat(actualOperations) + .contains( + Map.entry("test-topic_publish_testFunction", publishOperation), + Map.entry("test-topic_subscribe_testFunction", subscribeOperation)); + assertThat(schemasService.getMessages()).contains(Map.entry(String.class.getName(), publishMessage)); + assertThat(schemasService.getMessages()).contains(Map.entry(Integer.class.getName(), subscribeMessage)); } @TestConfiguration diff --git a/springwolf-plugins/springwolf-jms-plugin/build.gradle b/springwolf-plugins/springwolf-jms-plugin/build.gradle index 06343fd23..f4c9b11dd 100644 --- a/springwolf-plugins/springwolf-jms-plugin/build.gradle +++ b/springwolf-plugins/springwolf-jms-plugin/build.gradle @@ -7,11 +7,11 @@ plugins { } dependencies { + api project(":springwolf-asyncapi") api project(":springwolf-core") implementation "jakarta.jms:jakarta.jms-api" - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" runtimeOnly "org.apache.activemq:activemq-broker" @@ -28,11 +28,15 @@ dependencies { permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" testRuntimeOnly "org.springframework.boot:spring-boot-starter-web" + testImplementation "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}" + testImplementation "org.assertj:assertj-core:${assertjCoreVersion}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" @@ -45,7 +49,9 @@ dependencies { testImplementation "org.springframework.boot:spring-boot-test" testImplementation "org.springframework.boot:spring-boot-test-autoconfigure" + testImplementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testCompileOnly "org.projectlombok:lombok:${lombokVersion}" } jar { diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java index 27ca0cd55..b752696b3 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/jms/SpringwolfJmsScannerConfiguration.java @@ -7,7 +7,9 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.JmsOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -46,6 +48,20 @@ public SimpleChannelsScanner simpleJmsMethodLevelListenerAnnotationChannelsScann return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty(name = SPRINGWOLF_SCANNER_JMS_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleJmsMethodLevelListenerAnnotationOperationsScanner( + SpringwolfClassScanner classScanner, + JmsBindingFactory jmsBindingBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + MethodLevelAnnotationOperationsScanner strategy = new MethodLevelAnnotationOperationsScanner<>( + JmsListener.class, jmsBindingBuilder, payloadClassExtractor, schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @Order(value = BindingProcessorPriority.PROTOCOL_BINDING) @ConditionalOnMissingBean diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/JmsBindingFactory.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/JmsBindingFactory.java index 5cc472193..7712edab5 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/JmsBindingFactory.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/JmsBindingFactory.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.JmsListenerUtil; +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 lombok.NoArgsConstructor; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.jms.annotation.JmsListener; @@ -22,17 +22,17 @@ public String getChannelName(JmsListener annotation) { } @Override - public Map buildChannelBinding(JmsListener annotation) { + public Map buildChannelBinding(JmsListener annotation) { return JmsListenerUtil.buildChannelBinding(annotation, stringValueResolver); } @Override - public Map buildOperationBinding(JmsListener annotation) { + public Map buildOperationBinding(JmsListener annotation) { return JmsListenerUtil.buildOperationBinding(annotation, stringValueResolver); } @Override - public Map buildMessageBinding(JmsListener annotation) { + public Map buildMessageBinding(JmsListener annotation) { return JmsListenerUtil.buildMessageBinding(); } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java index 7d8d4db85..e1eefd2d0 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.jms.JMSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSMessageBinding; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java index 9430829a5..adc759f5f 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessor.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSOperationBinding; public class JmsOperationBindingProcessor extends AbstractOperationBindingProcessor { diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java index 057296e97..2c9d50f7b 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtil.java @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.jms.JMSChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.jms.JMSMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding; +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.bindings.jms.JMSChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSOperationBinding; import lombok.extern.slf4j.Slf4j; import org.springframework.jms.annotation.JmsListener; import org.springframework.util.StringValueResolver; @@ -20,17 +20,17 @@ public static String getChannelName(JmsListener annotation, StringValueResolver return resolver.resolveStringValue(annotation.destination()); } - public static Map buildChannelBinding( + public static Map buildChannelBinding( JmsListener annotation, StringValueResolver resolver) { return Map.of("jms", new JMSChannelBinding()); } - public static Map buildOperationBinding( + public static Map buildOperationBinding( JmsListener annotation, StringValueResolver resolver) { return Map.of("jms", new JMSOperationBinding()); } - public static Map buildMessageBinding() { + public static Map buildMessageBinding() { return Map.of("jms", new JMSMessageBinding()); } } diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java index d57e4bffb..6e878d3ec 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfJmsControllerIntegrationTest.java @@ -53,7 +53,7 @@ "springwolf.docket.info.title=Title", "springwolf.docket.info.version=1.0", "springwolf.docket.servers.jms.protocol=jms", - "springwolf.docket.servers.jms.url=127.0.0.1", + "springwolf.docket.servers.jms.host=127.0.0.1", "springwolf.plugin.jms.publishing.enabled=true", "springwolf.use-fqn=true" }) @@ -78,7 +78,7 @@ class SpringwolfJmsControllerIntegrationTest { void setup() { when(springwolfJmsProducer.isEnabled()).thenReturn(true); - schemasService.register(PayloadDto.class); + schemasService.registerSchema(PayloadDto.class); } @Test @@ -86,11 +86,11 @@ void testControllerShouldReturnBadRequestIfPayloadIsEmpty() { try { String content = """ - { - "bindings": null, - "headers": null, - "payload": "" - }"""; + { + "bindings": null, + "headers": null, + "payload": "" + }"""; mvc.perform(post("/springwolf/jms/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -105,10 +105,10 @@ void testControllerShouldReturnBadRequestIfPayloadIsNotSet() { try { String content = """ - { - "bindings": null, - "headers": null - }"""; + { + "bindings": null, + "headers": null + }"""; mvc.perform(post("/springwolf/jms/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -124,11 +124,11 @@ void testControllerShouldReturnNotFoundIfNoJmsProducerIsEnabled() throws Excepti String content = """ - { - "bindings": null, - "headers": null, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }" - }"""; + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }" + }"""; mvc.perform(post("/springwolf/jms/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -141,12 +141,12 @@ void testControllerShouldCallJmsProducerIfOnlyPayloadIsSend() throws Exception { String content = """ - { - "bindings": null, - "headers": null, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", - "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" - }"""; + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" + }"""; mvc.perform(post("/springwolf/jms/publish") .param("topic", "test-topic") @@ -165,15 +165,15 @@ void testControllerShouldCallJmsProducerIfPayloadAndHeadersAreSend() throws Exce String content = """ - { - "bindings": null, - "headers": { - "some-header-key": "some-header-value" - }, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", - "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" - } - """; + { + "bindings": null, + "headers": { + "some-header-key": "some-header-value" + }, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfJmsControllerIntegrationTest$PayloadDto" + } + """; mvc.perform(post("/springwolf/jms/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java index 53765e63a..82cf43bac 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsMessageBindingProcessorTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.jms.JMSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSMessageBinding; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java index 99879973b..e3d3d6ba5 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/JmsOperationBindingProcessorTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.JmsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSOperationBinding; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtilTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtilTest.java index 783f87ce8..893c072e0 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtilTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/JmsListenerUtilTest.java @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.jms.JMSChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.jms.JMSMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.jms.JMSOperationBinding; +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.bindings.jms.JMSChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.jms.JMSOperationBinding; import org.assertj.core.util.Sets; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -45,8 +45,7 @@ void buildChannelBinding() { StringValueResolver resolver = mock(StringValueResolver.class); // when - Map channelBinding = - JmsListenerUtil.buildChannelBinding(annotation, resolver); + Map channelBinding = JmsListenerUtil.buildChannelBinding(annotation, resolver); // then assertEquals(1, channelBinding.size()); @@ -61,7 +60,7 @@ void buildOperationBinding() { StringValueResolver resolver = mock(StringValueResolver.class); // when - Map operationBinding = + Map operationBinding = JmsListenerUtil.buildOperationBinding(annotation, resolver); // then @@ -73,7 +72,7 @@ void buildOperationBinding() { @Test void buildMessageBinding() { // when - Map messageBinding = JmsListenerUtil.buildMessageBinding(); + Map messageBinding = JmsListenerUtil.buildMessageBinding(); // then assertEquals(1, messageBinding.size()); diff --git a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java index 60917ba72..7ae8be750 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfJmsProducerConfigurationIntegrationTest.java @@ -41,7 +41,7 @@ public class SpringwolfJmsProducerConfigurationIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.jms.publishing.enabled=true" }) @MockBeans( @@ -82,7 +82,7 @@ void springwolfJmsProducerShouldBePresentInSpringContext() { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.jms.publishing.enabled=false" }) @MockBeans( diff --git a/springwolf-plugins/springwolf-kafka-plugin/README.md b/springwolf-plugins/springwolf-kafka-plugin/README.md index d7a783a4d..d3e3d984f 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/README.md +++ b/springwolf-plugins/springwolf-kafka-plugin/README.md @@ -44,7 +44,7 @@ springwolf.docket.info.title=${spring.application.name} springwolf.docket.info.version=1.0.0 springwolf.docket.servers.kafka.protocol=kafka -springwolf.docket.servers.kafka.url=${kafka.bootstrap.servers:localhost:29092} +springwolf.docket.servers.kafka.host=${kafka.bootstrap.servers:localhost:29092} ``` The basePackage field must be set with the name of the package containing the classes to be scanned for `@KafkaListener` diff --git a/springwolf-plugins/springwolf-kafka-plugin/build.gradle b/springwolf-plugins/springwolf-kafka-plugin/build.gradle index 0ec1a9fe2..28329d5e3 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/build.gradle +++ b/springwolf-plugins/springwolf-kafka-plugin/build.gradle @@ -7,10 +7,11 @@ plugins { } dependencies { + api project(":springwolf-asyncapi") api project(":springwolf-core") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "io.swagger.core.v3:swagger-models:${swaggerVersion}" + permitUnusedDeclared "io.swagger.core.v3:swagger-models:${swaggerVersion}" implementation "org.apache.kafka:kafka-clients:${kafkaClientsVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" @@ -27,13 +28,19 @@ dependencies { permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testCompileOnly "org.projectlombok:lombok:${lombokVersion}" testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}" testRuntimeOnly "org.springframework.boot:spring-boot-starter-web" + testImplementation "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}" + testImplementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" + testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}" testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}" diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/kafka/SpringwolfKafkaScannerConfiguration.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/kafka/SpringwolfKafkaScannerConfiguration.java index ff605c2b2..d9c671993 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/kafka/SpringwolfKafkaScannerConfiguration.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/kafka/SpringwolfKafkaScannerConfiguration.java @@ -7,8 +7,11 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.KafkaOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.ClassLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.ClassLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeadersForKafkaBuilder; @@ -71,6 +74,30 @@ public SimpleChannelsScanner simpleKafkaClassLevelListenerAnnotationChannelsScan return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleKafkaClassLevelListenerAnnotationOperationScanner( + SpringwolfClassScanner classScanner, + KafkaBindingFactory kafkaBindingBuilder, + AsyncHeadersForKafkaBuilder asyncHeadersForKafkaBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + ClassLevelAnnotationOperationsScanner strategy = + new ClassLevelAnnotationOperationsScanner<>( + KafkaListener.class, + KafkaHandler.class, + kafkaBindingBuilder, + asyncHeadersForKafkaBuilder, + payloadClassExtractor, + schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @ConditionalOnProperty( name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, @@ -88,6 +115,23 @@ public SimpleChannelsScanner simpleKafkaMethodLevelListenerAnnotationChannelsSca return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty( + name = SPRINGWOLF_SCANNER_KAFKA_LISTENER_ENABLED, + havingValue = "true", + matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleKafkaMethodLevelListenerAnnotationOperationsScanner( + SpringwolfClassScanner classScanner, + KafkaBindingFactory kafkaBindingBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + MethodLevelAnnotationOperationsScanner strategy = new MethodLevelAnnotationOperationsScanner<>( + KafkaListener.class, kafkaBindingBuilder, payloadClassExtractor, schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @Order(value = BindingProcessorPriority.PROTOCOL_BINDING) @ConditionalOnMissingBean diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/KafkaBindingFactory.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/KafkaBindingFactory.java index 78c940818..360ef3923 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/KafkaBindingFactory.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/KafkaBindingFactory.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.KafkaListenerUtil; +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 lombok.NoArgsConstructor; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.kafka.annotation.KafkaListener; @@ -22,17 +22,17 @@ public String getChannelName(KafkaListener annotation) { } @Override - public Map buildChannelBinding(KafkaListener annotation) { + public Map buildChannelBinding(KafkaListener annotation) { return KafkaListenerUtil.buildChannelBinding(); } @Override - public Map buildOperationBinding(KafkaListener annotation) { + public Map buildOperationBinding(KafkaListener annotation) { return KafkaListenerUtil.buildOperationBinding(annotation, stringValueResolver); } @Override - public Map buildMessageBinding(KafkaListener annotation) { + public Map buildMessageBinding(KafkaListener annotation) { return KafkaListenerUtil.buildMessageBinding(); } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessor.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessor.java index a19a64c96..1aadfef14 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessor.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessor.java @@ -1,19 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding.KafkaAsyncMessageBinding; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.media.StringSchema; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.Schema; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; import java.util.Optional; public class KafkaMessageBindingProcessor implements MessageBindingProcessor, EmbeddedValueResolverAware { @@ -37,7 +38,9 @@ private ProcessedMessageBinding mapToMessageBinding(KafkaAsyncOperationBinding b KafkaAsyncMessageBinding messageBinding = bindingAnnotation.messageBinding(); KafkaMessageBinding.KafkaMessageBindingBuilder kafkaMessageBindingBuilder = KafkaMessageBinding.builder(); + kafkaMessageBindingBuilder.key(resolveSchemaOrNull(messageBinding)); + String bindingVersion = resolveOrNull(messageBinding.bindingVersion()); if (StringUtils.hasText(bindingVersion)) { kafkaMessageBindingBuilder.bindingVersion(bindingVersion); @@ -50,15 +53,17 @@ private String resolveOrNull(String stringValue) { return StringUtils.hasText(stringValue) ? resolver.resolveStringValue(stringValue) : null; } - private Schema resolveSchemaOrNull(KafkaAsyncMessageBinding messageBinding) { - Schema schemaDefinition = null; + private Schema resolveSchemaOrNull(KafkaAsyncMessageBinding messageBinding) { + Schema schemaDefinition = null; switch (messageBinding.key().type()) { case UNDEFINED_KEY: break; case STRING_KEY: - schemaDefinition = new StringSchema() - .example(messageBinding.key().example()) - .description(resolveOrNull(messageBinding.key().description())); + schemaDefinition = SchemaObject.builder() + .type("string") + .examples(List.of(messageBinding.key().example())) + .description(resolveOrNull(messageBinding.key().description())) + .build(); } return schemaDefinition; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessor.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessor.java index 361901d0f..315583896 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessor.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessor.java @@ -1,20 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; -import com.asyncapi.v2.schema.Schema; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.KafkaListenerUtil; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; import org.springframework.util.StringUtils; public class KafkaOperationBindingProcessor extends AbstractOperationBindingProcessor { @Override protected ProcessedOperationBinding mapToOperationBinding(KafkaAsyncOperationBinding bindingAnnotation) { String clientId = resolveOrNull(bindingAnnotation.clientId()); - Schema clientIdSchema = KafkaListenerUtil.buildKafkaClientIdSchema(clientId); + SchemaObject clientIdSchema = KafkaListenerUtil.buildKafkaClientIdSchema(clientId); String groupId = resolveOrNull(bindingAnnotation.groupId()); - Schema groupIdSchema = KafkaListenerUtil.buildKafkaGroupIdSchema(groupId); + SchemaObject groupIdSchema = KafkaListenerUtil.buildKafkaGroupIdSchema(groupId); KafkaOperationBinding.KafkaOperationBindingBuilder kafkaOperationBindingBuilder = KafkaOperationBinding.builder(); diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtil.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtil.java index 72afb3b2d..60e17698b 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtil.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtil.java @@ -1,14 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.kafka.KafkaChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; -import com.asyncapi.v2.schema.Schema; -import com.asyncapi.v2.schema.Type; +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.bindings.kafka.KafkaChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.Schema; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject; +import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaType; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.lang.Nullable; @@ -32,11 +33,11 @@ public static String getChannelName(KafkaListener annotation, StringValueResolve return resolvedTopics.get(0); } - public static Map buildChannelBinding() { + public static Map buildChannelBinding() { return Map.of("kafka", new KafkaChannelBinding()); } - public static Map buildOperationBinding( + public static Map buildOperationBinding( KafkaListener annotation, StringValueResolver resolver) { String groupId = resolver.resolveStringValue(annotation.groupId()); Schema groupIdSchema = buildKafkaGroupIdSchema(groupId); @@ -47,8 +48,8 @@ public static String getChannelName(KafkaListener annotation, StringValueResolve } @Nullable - public static Schema buildKafkaClientIdSchema(String clientId) { - Schema schema = createStringSchema(clientId); + public static SchemaObject buildKafkaClientIdSchema(String clientId) { + SchemaObject schema = createStringSchema(clientId); if (schema != null) { log.debug("Found client id: {}", clientId); @@ -60,8 +61,8 @@ public static Schema buildKafkaClientIdSchema(String clientId) { } @Nullable - public static Schema buildKafkaGroupIdSchema(String groupId) { - Schema schema = createStringSchema(groupId); + public static SchemaObject buildKafkaGroupIdSchema(String groupId) { + SchemaObject schema = createStringSchema(groupId); if (schema != null) { log.debug("Found group id: {}", groupId); @@ -73,17 +74,17 @@ public static Schema buildKafkaGroupIdSchema(String groupId) { } @Nullable - private static Schema createStringSchema(String value) { + private static SchemaObject createStringSchema(String value) { if (value != null && !value.isEmpty()) { - Schema schema = new Schema(); - schema.setEnumValue(List.of(value)); - schema.setType(Type.STRING); + SchemaObject schema = new SchemaObject(); + schema.setEnumValues(List.of(value)); + schema.setType(SchemaType.STRING); return schema; } return null; } - public static Map buildMessageBinding() { + public static Map buildMessageBinding() { return Map.of("kafka", new KafkaMessageBinding()); } } diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaConsumerData.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaConsumerData.java index fa4c43ebb..90df87a94 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaConsumerData.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaConsumerData.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.kafka.KafkaChannelBinding; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; import lombok.Builder; import java.util.Map; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaProducerData.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaProducerData.java index fcb651d6f..4f7ae264e 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaProducerData.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/types/KafkaProducerData.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.types; -import com.asyncapi.v2.binding.channel.kafka.KafkaChannelBinding; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; import io.github.stavshamir.springwolf.asyncapi.types.channel.operation.message.header.AsyncHeaders; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; import lombok.Builder; import java.util.Map; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfKafkaControllerIntegrationTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfKafkaControllerIntegrationTest.java index 8081e70c8..85275e8ca 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfKafkaControllerIntegrationTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/controller/SpringwolfKafkaControllerIntegrationTest.java @@ -53,7 +53,7 @@ "springwolf.docket.info.title=Title", "springwolf.docket.info.version=1.0", "springwolf.docket.servers.kafka.protocol=kafka", - "springwolf.docket.servers.kafka.url=127.0.0.1", + "springwolf.docket.servers.kafka.host=127.0.0.1", "springwolf.plugin.kafka.publishing.enabled=true", "springwolf.use-fqn=true" }) @@ -78,7 +78,7 @@ class SpringwolfKafkaControllerIntegrationTest { void setup() { when(springwolfKafkaProducer.isEnabled()).thenReturn(true); - schemasService.register(PayloadDto.class); + schemasService.registerSchema(PayloadDto.class); } @Test @@ -86,11 +86,11 @@ void testControllerShouldReturnBadRequestIfPayloadIsEmpty() { try { String content = """ - { - "bindings": null, - "headers": null, - "payload": "" - }"""; + { + "bindings": null, + "headers": null, + "payload": "" + }"""; mvc.perform(post("/springwolf/kafka/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -105,10 +105,10 @@ void testControllerShouldReturnBadRequestIfPayloadIsNotSet() { try { String content = """ - { - "bindings": null, - "headers": null - }"""; + { + "bindings": null, + "headers": null + }"""; mvc.perform(post("/springwolf/kafka/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -124,11 +124,11 @@ void testControllerShouldReturnNotFoundIfNoKafkaProducerIsEnabled() throws Excep String content = """ - { - "bindings": null, - "headers": null, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }" - }"""; + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }" + }"""; mvc.perform(post("/springwolf/kafka/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) .content(content)) @@ -141,12 +141,12 @@ void testControllerShouldCallKafkaProducerIfOnlyPayloadIsSend() throws Exception String content = """ - { - "bindings": null, - "headers": null, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", - "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" - }"""; + { + "bindings": null, + "headers": null, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" + }"""; mvc.perform(post("/springwolf/kafka/publish") .param("topic", "test-topic") @@ -165,15 +165,15 @@ void testControllerShouldCallKafkaProducerIfPayloadAndHeadersAreSend() throws Ex String content = """ - { - "bindings": null, - "headers": { - "some-header-key": "some-header-value" - }, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", - "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" - } - """; + { + "bindings": null, + "headers": { + "some-header-key": "some-header-value" + }, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" + } + """; mvc.perform(post("/springwolf/kafka/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) @@ -193,16 +193,16 @@ void testControllerShouldCallKafkaProducerIfPayloadAndHeadersAndBindingsAreSend( String content = """ - { - "bindings": { - "key": "kafka-key-value" - }, - "headers": { - "some-header-key": "some-header-value" - }, - "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", - "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" - }"""; + { + "bindings": { + "key": "kafka-key-value" + }, + "headers": { + "some-header-key": "some-header-value" + }, + "payload": "{ \\"some-payload-key\\" : \\"some-payload-value\\" }", + "payloadType": "io.github.stavshamir.springwolf.asyncapi.controller.SpringwolfKafkaControllerIntegrationTest$PayloadDto" + }"""; mvc.perform(post("/springwolf/kafka/publish?topic=test-topic") .contentType(MediaType.APPLICATION_JSON) diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessorTest.java index 10de169ef..ac5297d34 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaMessageBindingProcessorTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessorTest.java index 9328c4d71..54a5bf154 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/KafkaOperationBindingProcessorTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.KafkaAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtilTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtilTest.java index 07d67fcd9..ac3b9a8bb 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtilTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/KafkaListenerUtilTest.java @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.kafka.KafkaChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.kafka.KafkaMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.kafka.KafkaOperationBinding; +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.bindings.kafka.KafkaChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.kafka.KafkaOperationBinding; import org.assertj.core.util.Arrays; import org.assertj.core.util.Sets; import org.junit.jupiter.api.Test; @@ -41,7 +41,7 @@ void getChannelName() { @Test void buildChannelBinding() { // when - Map channelBinding = KafkaListenerUtil.buildChannelBinding(); + Map channelBinding = KafkaListenerUtil.buildChannelBinding(); // then assertEquals(1, channelBinding.size()); @@ -59,8 +59,7 @@ void buildOperationBinding() { when(resolver.resolveStringValue("${group-id}")).thenReturn("group-id"); // when - Map operationBinding = - KafkaListenerUtil.buildOperationBinding(annotation, resolver); + Map operationBinding = KafkaListenerUtil.buildOperationBinding(annotation, resolver); // then assertEquals(1, operationBinding.size()); @@ -75,7 +74,7 @@ void buildOperationBinding() { @Test void buildMessageBinding() { // when - Map messageBinding = KafkaListenerUtil.buildMessageBinding(); + Map messageBinding = KafkaListenerUtil.buildMessageBinding(); // then assertEquals(1, messageBinding.size()); diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java index b024317cc..65a6bb898 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfKafkaProducerConfigurationIntegrationTest.java @@ -39,7 +39,7 @@ public class SpringwolfKafkaProducerConfigurationIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.kafka.publishing.enabled=true" }) @MockBeans( @@ -78,7 +78,7 @@ void springwolfKafkaTemplateShouldBePresentInSpringContext() { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.kafka.publishing.enabled=false" }) @MockBeans( diff --git a/springwolf-plugins/springwolf-sns-plugin/build.gradle b/springwolf-plugins/springwolf-sns-plugin/build.gradle index 570fc61a3..b04185a4e 100644 --- a/springwolf-plugins/springwolf-sns-plugin/build.gradle +++ b/springwolf-plugins/springwolf-sns-plugin/build.gradle @@ -13,9 +13,9 @@ dependencyManagement { } dependencies { + api project(":springwolf-asyncapi") api project(":springwolf-core") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation 'io.awspring.cloud:spring-cloud-aws-sns' @@ -32,6 +32,8 @@ dependencies { permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessor.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessor.java index 9194c2a95..74274e933 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessor.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata; -import com.asyncapi.v2.binding.message.sns.SNSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSMessageBinding; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessor.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessor.java index 1d4f39d7f..8f0ae4904 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessor.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessor.java @@ -1,15 +1,56 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata; -import com.asyncapi.v2.binding.operation.sns.SNSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.AbstractOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBindingIdentifier; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBindingConsumer; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBindingIdentifier; + +import java.util.List; public class SnsOperationBindingProcessor extends AbstractOperationBindingProcessor { @Override protected ProcessedOperationBinding mapToOperationBinding(SnsAsyncOperationBinding bindingAnnotation) { - return new ProcessedOperationBinding(bindingAnnotation.type(), new SNSOperationBinding()); + var identifier = convertAnnotation(bindingAnnotation.endpoint()); + var protocol = readProtocol(bindingAnnotation.protocol()); + + var consumer = SNSOperationBindingConsumer.builder() + .protocol(protocol) + .endpoint(identifier) + .rawMessageDelivery(bindingAnnotation.rawMessageDelivery()) + .build(); + var snsOperationBinding = + SNSOperationBinding.builder().consumers(List.of(consumer)).build(); + return new ProcessedOperationBinding(bindingAnnotation.type(), snsOperationBinding); + } + + private SNSOperationBindingConsumer.Protocol readProtocol(String protocol) { + return SNSOperationBindingConsumer.Protocol.valueOf(protocol.toUpperCase()); + } + + private SNSOperationBindingIdentifier convertAnnotation(SnsAsyncOperationBindingIdentifier identifier) { + var builder = SNSOperationBindingIdentifier.builder(); + + if (!identifier.url().isBlank()) { + builder.url(identifier.url()); + } + if (!identifier.arn().isBlank()) { + builder.arn(identifier.arn()); + } + if (!identifier.name().isBlank()) { + builder.name(identifier.name()); + } + if (!identifier.email().isBlank()) { + builder.email(identifier.email()); + } + if (!identifier.phone().isBlank()) { + builder.phone(identifier.phone()); + } + + return builder.build(); } } diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java index 28d8d06a1..18b7a6570 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBinding.java @@ -20,4 +20,10 @@ public @interface SnsAsyncOperationBinding { String type() default "sns"; + + String protocol(); + + SnsAsyncOperationBindingIdentifier endpoint(); + + boolean rawMessageDelivery() default true; } diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBindingIdentifier.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBindingIdentifier.java new file mode 100644 index 000000000..10f58c692 --- /dev/null +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SnsAsyncOperationBindingIdentifier.java @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation; + +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBindingIdentifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @see SNSOperationBindingIdentifier + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +@Inherited +public @interface SnsAsyncOperationBindingIdentifier { + /** + * Optional. The endpoint is a URL + */ + String url() default ""; + /** + * Optional. The endpoint is an email address + */ + String email() default ""; + /** + * Optional. The endpoint is a phone number + */ + String phone() default ""; + /** + * Optional. The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: + * "arn:aws:sqs:{region}:{account-id}:{queueName}" + */ + String arn() default ""; + /** + * Optional. The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a + * binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name + * refers to the name field sqs binding. We don't use $ref because we are referring, not including. + */ + String name() default ""; +} diff --git a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessorTest.java index 8bb7cd1d0..a948af451 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsMessageBindingProcessorTest.java @@ -1,9 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata; -import com.asyncapi.v2.binding.message.sns.SNSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBindingIdentifier; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSMessageBinding; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; @@ -33,7 +34,7 @@ void processWithoutAnnotationTest() throws NoSuchMethodException { assertThat(binding).isNotPresent(); } - @SnsAsyncOperationBinding + @SnsAsyncOperationBinding(protocol = "sqs", endpoint = @SnsAsyncOperationBindingIdentifier()) public void methodWithAnnotation() {} public void methodWithoutAnnotation() {} diff --git a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessorTest.java index 5da3b8643..62c4b1bd8 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/SnsOperationBindingProcessorTest.java @@ -1,11 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata; -import com.asyncapi.v2.binding.operation.sns.SNSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SnsAsyncOperationBindingIdentifier; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBindingConsumer; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sns.SNSOperationBindingIdentifier; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; class SnsOperationBindingProcessorTest { @@ -19,10 +24,18 @@ void mapToOperationBindingTest() throws NoSuchMethodException { ProcessedOperationBinding binding = processor.mapToOperationBinding(annotation); + var expectedOperation = SNSOperationBinding.builder() + .consumers(List.of(SNSOperationBindingConsumer.builder() + .protocol(SNSOperationBindingConsumer.Protocol.SQS) + .endpoint(SNSOperationBindingIdentifier.builder().build()) + .rawMessageDelivery(true) + .build())) + .build(); + assertThat(binding.getType()).isEqualTo("sns"); - assertThat(binding.getBinding()).isEqualTo(new SNSOperationBinding()); + assertThat(binding.getBinding()).isEqualTo(expectedOperation); } - @SnsAsyncOperationBinding + @SnsAsyncOperationBinding(protocol = "sqs", endpoint = @SnsAsyncOperationBindingIdentifier()) public void methodWithAnnotation() {} } diff --git a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java index a59ccdf8e..98374c386 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSnsProducerConfigurationIntegrationTest.java @@ -41,7 +41,7 @@ public class SpringwolfSnsProducerConfigurationIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.sns.publishing.enabled=true" }) @MockBeans( @@ -82,7 +82,7 @@ void springwolfSqsProducerShouldBePresentInSpringContext() { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.sns.publishing.enabled=false" }) @MockBeans( diff --git a/springwolf-plugins/springwolf-sqs-plugin/build.gradle b/springwolf-plugins/springwolf-sqs-plugin/build.gradle index 785658f9b..e918a01db 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/build.gradle +++ b/springwolf-plugins/springwolf-sqs-plugin/build.gradle @@ -13,9 +13,9 @@ dependencyManagement { } dependencies { + api project(":springwolf-asyncapi") api project(":springwolf-core") - implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}" implementation "org.slf4j:slf4j-api:${slf4jApiVersion}" implementation 'io.awspring.cloud:spring-cloud-aws-sqs' @@ -31,6 +31,8 @@ dependencies { permitUnusedDeclared "com.google.code.findbugs:jsr305:${jsr305Version}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/SqsBindingFactory.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/SqsBindingFactory.java index 44b2bd205..1e3261a3f 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/SqsBindingFactory.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/SqsBindingFactory.java @@ -1,11 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; import io.awspring.cloud.sqs.annotation.SqsListener; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.SqsListenerUtil; +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 lombok.NoArgsConstructor; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; @@ -22,17 +22,17 @@ public String getChannelName(SqsListener annotation) { } @Override - public Map buildChannelBinding(SqsListener annotation) { + public Map buildChannelBinding(SqsListener annotation) { return SqsListenerUtil.buildChannelBinding(annotation, stringValueResolver); } @Override - public Map buildOperationBinding(SqsListener annotation) { + public Map buildOperationBinding(SqsListener annotation) { return SqsListenerUtil.buildOperationBinding(annotation, stringValueResolver); } @Override - public Map buildMessageBinding(SqsListener annotation) { + public Map buildMessageBinding(SqsListener annotation) { return SqsListenerUtil.buildMessageBinding(); } diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessor.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessor.java index a2462422a..0d3366056 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessor.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessor.java @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.sqs.SQSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.MessageBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSMessageBinding; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessor.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessor.java index d94a507bb..76b1b4d1d 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessor.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessor.java @@ -1,14 +1,27 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.sqs.SQSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncQueueBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSChannelBindingQueue; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSOperationBinding; + +import java.util.ArrayList; +import java.util.List; public class SqsOperationBindingProcessor extends AbstractOperationBindingProcessor { @Override protected ProcessedOperationBinding mapToOperationBinding(SqsAsyncOperationBinding bindingAnnotation) { - return new ProcessedOperationBinding(bindingAnnotation.type(), new SQSOperationBinding()); + List queues = new ArrayList<>(); + for (SqsAsyncQueueBinding queue : bindingAnnotation.queues()) { + queues.add(SQSChannelBindingQueue.builder() + .name(queue.name()) + .fifoQueue(queue.fifoQueue()) + .build()); + } + var operationBinding = SQSOperationBinding.builder().queues(queues).build(); + return new ProcessedOperationBinding(bindingAnnotation.type(), operationBinding); } } diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtil.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtil.java index acbfd0fc9..9c43b2bae 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtil.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtil.java @@ -1,17 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.sqs.SQSChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.sqs.SQSMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.sqs.SQSOperationBinding; import io.awspring.cloud.sqs.annotation.SqsListener; +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.bindings.sqs.SQSChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSChannelBindingQueue; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSOperationBinding; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringValueResolver; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -25,17 +28,43 @@ public static String getChannelName(SqsListener annotation, StringValueResolver .orElseThrow(() -> new IllegalArgumentException("No queue name was found in @SqsListener annotation")); } - public static Map buildChannelBinding( + public static Map buildChannelBinding( SqsListener annotation, StringValueResolver resolver) { - return Map.of("sqs", new SQSChannelBinding()); + var queueName = readAnnotationQueueNames(annotation)[0]; + + var queue = + SQSChannelBindingQueue.builder().name(queueName).fifoQueue(true).build(); + var channelBinding = SQSChannelBinding.builder().queue(queue).build(); + return Map.of("sqs", channelBinding); } - public static Map buildOperationBinding( + public static Map buildOperationBinding( SqsListener annotation, StringValueResolver resolver) { - return Map.of("sqs", new SQSOperationBinding()); + List queues = new ArrayList<>(); + + for (String queueName : readAnnotationQueueNames(annotation)) { + queues.add(SQSChannelBindingQueue.builder() + .name(queueName) + .fifoQueue(true) + .build()); + } + var operationBinding = SQSOperationBinding.builder().queues(queues).build(); + return Map.of("sqs", operationBinding); } - public static Map buildMessageBinding() { + public static Map buildMessageBinding() { return Map.of("sqs", new SQSMessageBinding()); } + + private static String[] readAnnotationQueueNames(SqsListener annotation) { + String[] queueNames; + if (annotation.value().length > 0) { + queueNames = annotation.value(); + } else if (annotation.queueNames().length > 0) { + queueNames = annotation.queueNames(); + } else { + queueNames = new String[] {"default-queue-name"}; + } + return queueNames; + } } diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java index ebfae1f60..69d1fed04 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncOperationBinding.java @@ -20,4 +20,6 @@ public @interface SqsAsyncOperationBinding { String type() default "sqs"; + + SqsAsyncQueueBinding[] queues() default {}; } diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncQueueBinding.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncQueueBinding.java new file mode 100644 index 000000000..5e34b2548 --- /dev/null +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/operationdata/annotation/SqsAsyncQueueBinding.java @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +@Inherited +public @interface SqsAsyncQueueBinding { + + String name() default ""; + + boolean fifoQueue() default true; + + int deliveryDelay() default 0; +} diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/sqs/SpringwolfSqsScannerConfiguration.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/sqs/SpringwolfSqsScannerConfiguration.java index 821df83a4..501fedf85 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/sqs/SpringwolfSqsScannerConfiguration.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/stavshamir/springwolf/asyncapi/sqs/SpringwolfSqsScannerConfiguration.java @@ -8,7 +8,9 @@ import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.SqsOperationBindingProcessor; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.ChannelPriority; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.SimpleOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationChannelsScanner; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation.MethodLevelAnnotationOperationsScanner; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.payload.PayloadClassExtractor; import io.github.stavshamir.springwolf.asyncapi.scanners.classes.SpringwolfClassScanner; import io.github.stavshamir.springwolf.schemas.SchemasService; @@ -46,6 +48,20 @@ public SimpleChannelsScanner simpleSqsMethodLevelListenerAnnotationChannelsScann return new SimpleChannelsScanner(classScanner, strategy); } + @Bean + @ConditionalOnProperty(name = SPRINGWOLF_SCANNER_SQS_LISTENER_ENABLED, havingValue = "true", matchIfMissing = true) + @Order(value = ChannelPriority.AUTO_DISCOVERED) + public SimpleOperationsScanner simpleSqsMethodLevelListenerAnnotationOperationsScanner( + SpringwolfClassScanner classScanner, + SqsBindingFactory sqsBindingBuilder, + PayloadClassExtractor payloadClassExtractor, + SchemasService schemasService) { + MethodLevelAnnotationOperationsScanner strategy = new MethodLevelAnnotationOperationsScanner<>( + SqsListener.class, sqsBindingBuilder, payloadClassExtractor, schemasService); + + return new SimpleOperationsScanner(classScanner, strategy); + } + @Bean @Order(value = BindingProcessorPriority.PROTOCOL_BINDING) @ConditionalOnMissingBean diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessorTest.java b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessorTest.java index 77ab05262..3f1d4a36a 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsMessageBindingProcessorTest.java @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.message.sqs.SQSMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedMessageBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSMessageBinding; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessorTest.java b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessorTest.java index 3289547ff..1fd83fa88 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessorTest.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/bindings/processor/SqsOperationBindingProcessorTest.java @@ -1,11 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor; -import com.asyncapi.v2.binding.operation.sqs.SQSOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding; import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncOperationBinding; +import io.github.stavshamir.springwolf.asyncapi.scanners.channels.operationdata.annotation.SqsAsyncQueueBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSChannelBindingQueue; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSOperationBinding; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; class SqsOperationBindingProcessorTest { @@ -19,10 +23,17 @@ void mapToOperationBindingTest() throws NoSuchMethodException { ProcessedOperationBinding binding = processor.mapToOperationBinding(annotation); + var expectedOperation = SQSOperationBinding.builder() + .queues(List.of(SQSChannelBindingQueue.builder() + .name("queue-name") + .fifoQueue(true) + .build())) + .build(); + assertThat(binding.getType()).isEqualTo("sqs"); - assertThat(binding.getBinding()).isEqualTo(new SQSOperationBinding()); + assertThat(binding.getBinding()).isEqualTo(expectedOperation); } - @SqsAsyncOperationBinding + @SqsAsyncOperationBinding(queues = {@SqsAsyncQueueBinding(name = "queue-name")}) public void methodWithAnnotation() {} } diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtilTest.java b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtilTest.java index cb93ca375..cdffa7130 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtilTest.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/asyncapi/scanners/channels/annotation/SqsListenerUtilTest.java @@ -1,18 +1,20 @@ // SPDX-License-Identifier: Apache-2.0 package io.github.stavshamir.springwolf.asyncapi.scanners.channels.annotation; -import com.asyncapi.v2.binding.channel.ChannelBinding; -import com.asyncapi.v2.binding.channel.sqs.SQSChannelBinding; -import com.asyncapi.v2.binding.message.MessageBinding; -import com.asyncapi.v2.binding.message.sqs.SQSMessageBinding; -import com.asyncapi.v2.binding.operation.OperationBinding; -import com.asyncapi.v2.binding.operation.sqs.SQSOperationBinding; import io.awspring.cloud.sqs.annotation.SqsListener; +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.bindings.sqs.SQSChannelBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSChannelBindingQueue; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSMessageBinding; +import io.github.stavshamir.springwolf.asyncapi.v3.bindings.sqs.SQSOperationBinding; import org.assertj.core.util.Sets; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.util.StringValueResolver; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,13 +47,19 @@ void buildChannelBinding() { StringValueResolver resolver = mock(StringValueResolver.class); // when - Map channelBinding = - SqsListenerUtil.buildChannelBinding(annotation, resolver); + Map channelBinding = SqsListenerUtil.buildChannelBinding(annotation, resolver); // then + var expectedChannel = SQSChannelBinding.builder() + .queue(SQSChannelBindingQueue.builder() + .name("${queue-1}") + .fifoQueue(true) + .build()) + .build(); + assertEquals(1, channelBinding.size()); assertEquals(Sets.newTreeSet("sqs"), channelBinding.keySet()); - assertEquals(new SQSChannelBinding(), channelBinding.get("sqs")); + assertEquals(expectedChannel, channelBinding.get("sqs")); } @Test @@ -61,19 +69,26 @@ void buildOperationBinding() { StringValueResolver resolver = mock(StringValueResolver.class); // when - Map operationBinding = + Map operationBinding = SqsListenerUtil.buildOperationBinding(annotation, resolver); // then + var expectedOperation = SQSOperationBinding.builder() + .queues(List.of(SQSChannelBindingQueue.builder() + .name("${queue-1}") + .fifoQueue(true) + .build())) + .build(); + assertEquals(1, operationBinding.size()); assertEquals(Sets.newTreeSet("sqs"), operationBinding.keySet()); - assertEquals(new SQSOperationBinding(), operationBinding.get("sqs")); + assertEquals(expectedOperation, operationBinding.get("sqs")); } @Test void buildMessageBinding() { // when - Map messageBinding = SqsListenerUtil.buildMessageBinding(); + Map messageBinding = SqsListenerUtil.buildMessageBinding(); // then assertEquals(1, messageBinding.size()); diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java index 8abf87714..683934882 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/test/java/io/github/stavshamir/springwolf/configuration/SpringwolfSqsProducerConfigurationIntegrationTest.java @@ -41,7 +41,7 @@ public class SpringwolfSqsProducerConfigurationIntegrationTest { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.sqs.publishing.enabled=true" }) @MockBeans( @@ -82,7 +82,7 @@ void springwolfSqsProducerShouldBePresentInSpringContext() { "springwolf.docket.info.version=1.0.0", "springwolf.docket.base-package=io.github.stavshamir.springwolf.example", "springwolf.docket.servers.test-protocol.protocol=test", - "springwolf.docket.servers.test-protocol.url=some-server:1234", + "springwolf.docket.servers.test-protocol.host=some-server:1234", "springwolf.plugin.sqs.publishing.enabled=false" }) @MockBeans( diff --git a/springwolf-ui/README.md b/springwolf-ui/README.md index 8545de722..95bbe6f14 100644 --- a/springwolf-ui/README.md +++ b/springwolf-ui/README.md @@ -16,6 +16,13 @@ dependencies { After starting the application, visit: `localhost:8080/springwolf/asyncapi-ui.html`. +## TODOs: + +- Migrate to AsyncApi 3 +- Review Angular compoents - adapt to latest angular guidelines +- Migrate tslint to eslint +- Validate existing documents using parser-js? + ## Development 1. Run `npm i` 2. Run `ng serve` @@ -25,8 +32,6 @@ After starting the application, visit: `localhost:8080/springwolf/asyncapi-ui.ht The application renders content based on mock data in `src/app/shared/mock`. It contains multiple mocks - including the ones from the springwolf-core examples projects. -To update the mock data, run `npm run update-mocks`. - ## Release Releasing is done by running the gradle task `publish`. For local development, use `publishToMavenLocal`. diff --git a/springwolf-ui/angular.json b/springwolf-ui/angular.json index 86aeea64b..74e216b8e 100644 --- a/springwolf-ui/angular.json +++ b/springwolf-ui/angular.json @@ -27,7 +27,7 @@ "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/font-awesome/css/font-awesome.min.css", - "src/styles.css" + "src/main.css" ], "scripts": [] }, @@ -91,7 +91,7 @@ ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.css" + "src/main.css" ], "scripts": [] } diff --git a/springwolf-ui/browserslist b/springwolf-ui/browserslist index 80848532e..1cf31eca5 100644 --- a/springwolf-ui/browserslist +++ b/springwolf-ui/browserslist @@ -9,4 +9,3 @@ last 2 versions Firefox ESR not dead -not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/springwolf-ui/src/app/app.component.css b/springwolf-ui/src/app/app.component.css index 7294c4b3c..45f9002fd 100644 --- a/springwolf-ui/src/app/app.component.css +++ b/springwolf-ui/src/app/app.component.css @@ -7,5 +7,5 @@ main { margin: 0 64px; - padding: 64px 0; + padding-bottom: 32px; } diff --git a/springwolf-ui/src/app/app.module.ts b/springwolf-ui/src/app/app.module.ts index f2ca8886a..180538718 100644 --- a/springwolf-ui/src/app/app.module.ts +++ b/springwolf-ui/src/app/app.module.ts @@ -7,20 +7,22 @@ import { HttpClientInMemoryWebApiModule } from "angular-in-memory-web-api"; import { HighlightModule, HIGHLIGHT_OPTIONS } from "ngx-highlightjs"; import { environment } from "./../environments/environment"; import { AppComponent } from "./app.component"; -import { ChannelMainComponent } from "./channels/channel-main/channel-main.component"; -import { ChannelsComponent } from "./channels/channels.component"; -import { HeaderComponent } from "./header/header.component"; -import { InfoComponent } from "./info/info.component"; +import { ChannelMainComponent } from "./components/channels/channel-main/channel-main.component"; +import { ChannelsComponent } from "./components/channels/channels.component"; +import { HeaderComponent } from "./components/header/header.component"; +import { InfoComponent } from "./components/info/info.component"; import { MaterialModule } from "./material.module"; -import { SchemaComponent } from "./schemas/schema/schema.component"; -import { SchemasComponent } from "./schemas/schemas.component"; -import { ServersComponent } from "./servers/servers.component"; -import { AsyncApiService } from "./shared/asyncapi.service"; -import { MockServer } from "./shared/mock/mock-server"; -import { PublisherService } from "./shared/publisher.service"; +import { SchemaComponent } from "./components/schemas/schema/schema.component"; +import { SchemaRangeComponent } from "./components/schemas/range/schema-range.component"; +import { SchemasComponent } from "./components/schemas/schemas.component"; +import { ServersComponent } from "./components/servers/servers.component"; +import { AsyncApiService } from "./service/asyncapi/asyncapi.service"; +import { MockServer } from "./service/mock/mock-server"; +import { PublisherService } from "./service/publisher.service"; +import { NotificationService } from "./service/notification.service"; import { FormsModule } from "@angular/forms"; -import { JsonComponent } from "./shared/components/json/json.component"; -import { AsyncApiMapperService } from "./shared/asyncapi-mapper.service"; +import { JsonComponent } from "./components/json/json.component"; +import { AsyncApiMapperService } from "./service/asyncapi/asyncapi-mapper.service"; @NgModule({ declarations: [ @@ -32,6 +34,7 @@ import { AsyncApiMapperService } from "./shared/asyncapi-mapper.service"; ChannelMainComponent, SchemasComponent, SchemaComponent, + SchemaRangeComponent, JsonComponent, ], imports: [ @@ -48,6 +51,7 @@ import { AsyncApiMapperService } from "./shared/asyncapi-mapper.service"; providers: [ AsyncApiService, AsyncApiMapperService, + NotificationService, PublisherService, { provide: HIGHLIGHT_OPTIONS, diff --git a/springwolf-ui/src/app/channels/channel-main/channel-main.component.css b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.css similarity index 100% rename from springwolf-ui/src/app/channels/channel-main/channel-main.component.css rename to springwolf-ui/src/app/components/channels/channel-main/channel-main.component.css diff --git a/springwolf-ui/src/app/channels/channel-main/channel-main.component.html b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.html similarity index 82% rename from springwolf-ui/src/app/channels/channel-main/channel-main.component.html rename to springwolf-ui/src/app/components/channels/channel-main/channel-main.component.html index 01b1b509f..7dfc7670d 100644 --- a/springwolf-ui/src/app/channels/channel-main/channel-main.component.html +++ b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.html @@ -9,7 +9,10 @@

{{ operation.message.description }}

Message Binding

@@ -19,12 +22,12 @@

Message Binding

[rows]="messageBindingExampleTextAreaLineCount" [value]=" createMessageBindingExample( - operation.message.bindings.get(protocolName) + operation.message.bindings.get(operation.protocol) )?.value " (keyup)=" recalculateLineCount( - 'massageBindingExample', + 'messageBindingExample', bindingTextArea.value ) " @@ -74,7 +77,7 @@

Message

headersTextArea.value = headersExample?.value; headersTextAreaLineCount = headersExample?.lineCount || 0; bindingTextArea.value = createMessageBindingExample( - operation.message.bindings.get(protocolName) + operation.message.bindings.get(operation.protocol) )?.value; messageBindingExampleTextAreaLineCount = messageBindingExample?.lineCount || 0 @@ -114,10 +117,19 @@

- +
+ +
- +
+ +
diff --git a/springwolf-ui/src/app/channels/channel-main/channel-main.component.ts b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.ts similarity index 84% rename from springwolf-ui/src/app/channels/channel-main/channel-main.component.ts rename to springwolf-ui/src/app/components/channels/channel-main/channel-main.component.ts index abb1e6134..0d85e0205 100644 --- a/springwolf-ui/src/app/channels/channel-main/channel-main.component.ts +++ b/springwolf-ui/src/app/components/channels/channel-main/channel-main.component.ts @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Component, Input, OnInit } from "@angular/core"; -import { AsyncApiService } from "src/app/shared/asyncapi.service"; -import { Example } from "src/app/shared/models/example.model"; -import { Schema } from "src/app/shared/models/schema.model"; -import { PublisherService } from "src/app/shared/publisher.service"; +import { AsyncApiService } from "src/app/service/asyncapi/asyncapi.service"; +import { Example } from "src/app/models/example.model"; +import { Schema } from "src/app/models/schema.model"; +import { PublisherService } from "src/app/service/publisher.service"; import { MatSnackBar } from "@angular/material/snack-bar"; -import { MessageBinding, Operation } from "src/app/shared/models/channel.model"; +import { Operation } from "src/app/models/operation.model"; +import { Binding } from "src/app/models/bindings.model"; import { STATUS } from "angular-in-memory-web-api"; @Component({ @@ -27,7 +28,6 @@ export class ChannelMainComponent implements OnInit { headersSchemaIdentifier: string; headersExample: Example; headersTextAreaLineCount: number; - protocolName: string; messageBindingExample?: Example; messageBindingExampleTextAreaLineCount: number; @@ -58,8 +58,6 @@ export class ChannelMainComponent implements OnInit { this.messageBindingExampleTextAreaLineCount = this.messageBindingExample?.lineCount || 0; }); - - this.protocolName = Object.keys(this.operation.bindings)[0]; } isEmptyObject(object?: any): boolean { @@ -70,9 +68,7 @@ export class ChannelMainComponent implements OnInit { ); } - createMessageBindingExample( - messageBinding?: MessageBinding - ): Example | undefined { + createMessageBindingExample(messageBinding?: Binding): Example | undefined { if (messageBinding === undefined || messageBinding === null) { return undefined; } @@ -93,12 +89,13 @@ export class ChannelMainComponent implements OnInit { return bindingExample; } - getExampleValue(bindingValue: string | Schema): any { + getExampleValue(bindingValue: string | Binding): any { if (typeof bindingValue === "string") { return bindingValue; - } else { - return bindingValue.example.value; + } else if (typeof bindingValue.examples === "object") { + return bindingValue.examples["0"]; } + return undefined; } recalculateLineCount(field: string, text: string): void { @@ -109,7 +106,7 @@ export class ChannelMainComponent implements OnInit { case "headers": this.headersTextAreaLineCount = text.split("\n").length; break; - case "massageBindingExample": + case "messageBindingExample": this.messageBindingExampleTextAreaLineCount = text.split("\n").length; break; } @@ -127,7 +124,7 @@ export class ChannelMainComponent implements OnInit { this.publisherService .publish( - this.protocolName, + this.operation.protocol, this.channelName, example, payloadType, @@ -158,7 +155,7 @@ export class ChannelMainComponent implements OnInit { private handlePublishError(err: { status?: number }) { let msg = "Publish failed"; if (err?.status === STATUS.NOT_FOUND) { - msg += ": no publisher was provided for " + this.protocolName; + msg += ": no publisher was provided for " + this.operation.protocol; } return this.snackBar.open(msg, "ERROR", { diff --git a/springwolf-ui/src/app/channels/channels.component.css b/springwolf-ui/src/app/components/channels/channels.component.css similarity index 93% rename from springwolf-ui/src/app/channels/channels.component.css rename to springwolf-ui/src/app/components/channels/channels.component.css index a369a8039..2b614bf46 100644 --- a/springwolf-ui/src/app/channels/channels.component.css +++ b/springwolf-ui/src/app/components/channels/channels.component.css @@ -7,11 +7,11 @@ display: inline-block; } -.subscribe-badge { +.send-badge { background-color: #ffd580; } -.publish-badge { +.receive-badge { background-color: #9bd279; } diff --git a/springwolf-ui/src/app/channels/channels.component.html b/springwolf-ui/src/app/components/channels/channels.component.html similarity index 58% rename from springwolf-ui/src/app/channels/channels.component.html rename to springwolf-ui/src/app/components/channels/channels.component.html index ec0fd1327..49844fcc5 100644 --- a/springwolf-ui/src/app/channels/channels.component.html +++ b/springwolf-ui/src/app/components/channels/channels.component.html @@ -1,29 +1,6 @@

Channels

-Semantics of publish and subscribe: -
    -
  • - - publish - - - means publish an event to the channel and this application will receive it -
  • -
  • - - means subscribe to this channel to receive events published by this - application -
  • -
- Channels
- {{ channel.operation.operation }} + {{ channel.operation.operationType }} this.setChannelSelectionFromLocation()); this.asyncApiService.getAsyncApi().subscribe((asyncapi) => { - this.channels = this.sortChannels(asyncapi.channels); + this.channels = this.sortChannels(asyncapi.channelOperations); }); } - private sortChannels(channels: Array): Array { + private sortChannels( + channels: Array + ): Array { return channels.sort((a, b) => { if (a.operation.protocol === b.operation.protocol) { - if (a.operation.operation === b.operation.operation) { + if (a.operation.operationType === b.operation.operationType) { if (a.name === b.name) { return a.operation.message.name.localeCompare( b.operation.message.name @@ -41,7 +46,9 @@ export class ChannelsComponent implements OnInit { return a.name.localeCompare(b.name); } } else { - return a.operation.operation.localeCompare(b.operation.operation); + return a.operation.operationType.localeCompare( + b.operation.operationType + ); } } else if (a.operation.protocol != null) { return a.operation.protocol.localeCompare(b.operation.protocol); @@ -51,7 +58,7 @@ export class ChannelsComponent implements OnInit { }); } - setChannelSelection(channel: Channel): void { + setChannelSelection(channel: ChannelOperation): void { window.location.hash = channel.anchorIdentifier; } setChannelSelectionFromLocation(): void { diff --git a/springwolf-ui/src/app/header/header.component.css b/springwolf-ui/src/app/components/header/header.component.css similarity index 100% rename from springwolf-ui/src/app/header/header.component.css rename to springwolf-ui/src/app/components/header/header.component.css diff --git a/springwolf-ui/src/app/header/header.component.html b/springwolf-ui/src/app/components/header/header.component.html similarity index 100% rename from springwolf-ui/src/app/header/header.component.html rename to springwolf-ui/src/app/components/header/header.component.html diff --git a/springwolf-ui/src/app/header/header.component.ts b/springwolf-ui/src/app/components/header/header.component.ts similarity index 100% rename from springwolf-ui/src/app/header/header.component.ts rename to springwolf-ui/src/app/components/header/header.component.ts diff --git a/springwolf-ui/src/app/info/info.component.css b/springwolf-ui/src/app/components/info/info.component.css similarity index 56% rename from springwolf-ui/src/app/info/info.component.css rename to springwolf-ui/src/app/components/info/info.component.css index b97bcb1a5..31cbcf0a0 100644 --- a/springwolf-ui/src/app/info/info.component.css +++ b/springwolf-ui/src/app/components/info/info.component.css @@ -2,3 +2,7 @@ p { /* empty */ } + +.info-chips > * { + margin-inline-end: 8px; +} diff --git a/springwolf-ui/src/app/components/info/info.component.html b/springwolf-ui/src/app/components/info/info.component.html new file mode 100644 index 000000000..6756f3e32 --- /dev/null +++ b/springwolf-ui/src/app/components/info/info.component.html @@ -0,0 +1,35 @@ + +

{{ info?.title }}

+
+ API version {{ info?.version || "not specified" }} + - + Download AsyncAPI file +
+ +

+ + + License: {{ info.license.name }} + + License: {{ info.license.name }} + + + + + {{ info.contact.url }} + + + + + {{ info.contact.email.name }} + + +

+ +

{{ info.description }}

diff --git a/springwolf-ui/src/app/info/info.component.ts b/springwolf-ui/src/app/components/info/info.component.ts similarity index 82% rename from springwolf-ui/src/app/info/info.component.ts rename to springwolf-ui/src/app/components/info/info.component.ts index f8545c9aa..28a1f1f7b 100644 --- a/springwolf-ui/src/app/info/info.component.ts +++ b/springwolf-ui/src/app/components/info/info.component.ts @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Component, OnInit } from "@angular/core"; -import { AsyncApi } from "../shared/models/asyncapi.model"; -import { Info } from "../shared/models/info.model"; -import { AsyncApiService } from "../shared/asyncapi.service"; +import { AsyncApi } from "../../models/asyncapi.model"; +import { Info } from "../../models/info.model"; +import { AsyncApiService } from "../../service/asyncapi/asyncapi.service"; @Component({ selector: "app-info", diff --git a/springwolf-ui/src/app/shared/components/json/json.component.ts b/springwolf-ui/src/app/components/json/json.component.ts similarity index 100% rename from springwolf-ui/src/app/shared/components/json/json.component.ts rename to springwolf-ui/src/app/components/json/json.component.ts diff --git a/springwolf-ui/src/app/components/schemas/range/schema-range.component.css b/springwolf-ui/src/app/components/schemas/range/schema-range.component.css new file mode 100644 index 000000000..e326ac5e4 --- /dev/null +++ b/springwolf-ui/src/app/components/schemas/range/schema-range.component.css @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +.range { + background-color: rgb(128, 90, 213); + color: rgb(255, 255, 255); + margin: 0 8 0 8; + padding: 2 4 2 4; + border-radius: 4px; +} diff --git a/springwolf-ui/src/app/components/schemas/range/schema-range.component.html b/springwolf-ui/src/app/components/schemas/range/schema-range.component.html new file mode 100644 index 000000000..61773d918 --- /dev/null +++ b/springwolf-ui/src/app/components/schemas/range/schema-range.component.html @@ -0,0 +1,59 @@ + +
+ + + {{ schema.minimum }} + + + + {{ schema.maximum }} + + + + {{ schema.minimum }} .. {{ schema.maximum }} + + +
+ +( +) +[ +] + +> +< +>= +<= diff --git a/springwolf-ui/src/app/components/schemas/range/schema-range.component.ts b/springwolf-ui/src/app/components/schemas/range/schema-range.component.ts new file mode 100644 index 000000000..b88a5f0da --- /dev/null +++ b/springwolf-ui/src/app/components/schemas/range/schema-range.component.ts @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { Component, Input } from "@angular/core"; +import { Schema } from "src/app/models/schema.model"; + +@Component({ + selector: "app-schema-range", + templateUrl: "./schema-range.component.html", + styleUrls: ["./schema-range.component.css"], +}) +export class SchemaRangeComponent { + @Input() schema: Schema; +} diff --git a/springwolf-ui/src/app/schemas/schema/schema.component.css b/springwolf-ui/src/app/components/schemas/schema/schema.component.css similarity index 86% rename from springwolf-ui/src/app/schemas/schema/schema.component.css rename to springwolf-ui/src/app/components/schemas/schema/schema.component.css index 92adde268..62281f999 100644 --- a/springwolf-ui/src/app/schemas/schema/schema.component.css +++ b/springwolf-ui/src/app/components/schemas/schema/schema.component.css @@ -12,6 +12,10 @@ color: red; } +.type-content { + display: flex; +} + .type { color: #55a; } diff --git a/springwolf-ui/src/app/components/schemas/schema/schema.component.html b/springwolf-ui/src/app/components/schemas/schema/schema.component.html new file mode 100644 index 000000000..421cd83ae --- /dev/null +++ b/springwolf-ui/src/app/components/schemas/schema/schema.component.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + +
+ {{ property.key }} + * + + + {{ property.value.items.type }}[] + + {{ property.value.items.refTitle }}[] + + + + +
+ + +
+ {{ value.type }} + + +
+ + + {{ value.refTitle }} + + ({{ value.format }}) +
{{ value.description }}
+ example: {{ value.example.value }} + + {{ enum }} + + +
diff --git a/springwolf-ui/src/app/schemas/schema/schema.component.ts b/springwolf-ui/src/app/components/schemas/schema/schema.component.ts similarity index 82% rename from springwolf-ui/src/app/schemas/schema/schema.component.ts rename to springwolf-ui/src/app/components/schemas/schema/schema.component.ts index c59fb2a89..e2ed2d11b 100644 --- a/springwolf-ui/src/app/schemas/schema/schema.component.ts +++ b/springwolf-ui/src/app/components/schemas/schema/schema.component.ts @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Component, Input } from "@angular/core"; -import { Schema } from "src/app/shared/models/schema.model"; +import { Schema } from "src/app/models/schema.model"; @Component({ selector: "app-schema", diff --git a/springwolf-ui/src/app/schemas/schemas.component.css b/springwolf-ui/src/app/components/schemas/schemas.component.css similarity index 55% rename from springwolf-ui/src/app/schemas/schemas.component.css rename to springwolf-ui/src/app/components/schemas/schemas.component.css index 575231555..0eae36c5b 100644 --- a/springwolf-ui/src/app/schemas/schemas.component.css +++ b/springwolf-ui/src/app/components/schemas/schemas.component.css @@ -9,3 +9,14 @@ h3 { padding: 6px; font-weight: normal; } + +.badge { +} + +.type-badge { + background-color: #e0e0e0; + border-radius: 4px; + padding: 4px; + font-weight: normal; + font-size: small; +} diff --git a/springwolf-ui/src/app/schemas/schemas.component.html b/springwolf-ui/src/app/components/schemas/schemas.component.html similarity index 72% rename from springwolf-ui/src/app/schemas/schemas.component.html rename to springwolf-ui/src/app/components/schemas/schemas.component.html index 4c8dc4563..3e2ac7500 100644 --- a/springwolf-ui/src/app/schemas/schemas.component.html +++ b/springwolf-ui/src/app/components/schemas/schemas.component.html @@ -11,8 +11,12 @@

Schemas

{{ schema.value.title }}

- - {{ schema.value.description }} + +
{{ schema.value.description }}
+
{{ schema.value.type }}

diff --git a/springwolf-ui/src/app/schemas/schemas.component.ts b/springwolf-ui/src/app/components/schemas/schemas.component.ts similarity index 88% rename from springwolf-ui/src/app/schemas/schemas.component.ts rename to springwolf-ui/src/app/components/schemas/schemas.component.ts index 6b525dc15..68c7fa0a4 100644 --- a/springwolf-ui/src/app/schemas/schemas.component.ts +++ b/springwolf-ui/src/app/components/schemas/schemas.component.ts @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Component, OnInit } from "@angular/core"; import { Location } from "@angular/common"; -import { AsyncApiService } from "../shared/asyncapi.service"; -import { Schema } from "../shared/models/schema.model"; +import { AsyncApiService } from "../../service/asyncapi/asyncapi.service"; +import { Schema } from "../../models/schema.model"; @Component({ selector: "app-schemas", diff --git a/springwolf-ui/src/app/servers/servers.component.css b/springwolf-ui/src/app/components/servers/servers.component.css similarity index 100% rename from springwolf-ui/src/app/servers/servers.component.css rename to springwolf-ui/src/app/components/servers/servers.component.css diff --git a/springwolf-ui/src/app/components/servers/servers.component.html b/springwolf-ui/src/app/components/servers/servers.component.html new file mode 100644 index 000000000..b69790125 --- /dev/null +++ b/springwolf-ui/src/app/components/servers/servers.component.html @@ -0,0 +1,25 @@ + +

Servers

+ + + {{ server.key }} + + + + + + + + + + + + + +
Host: + {{ server.value.host }} +
Protocol: + {{ server.value.protocol }} +
+
+
diff --git a/springwolf-ui/src/app/servers/servers.component.ts b/springwolf-ui/src/app/components/servers/servers.component.ts similarity index 79% rename from springwolf-ui/src/app/servers/servers.component.ts rename to springwolf-ui/src/app/components/servers/servers.component.ts index 049b09df6..63929561f 100644 --- a/springwolf-ui/src/app/servers/servers.component.ts +++ b/springwolf-ui/src/app/components/servers/servers.component.ts @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Component, OnInit } from "@angular/core"; -import { AsyncApiService } from "../shared/asyncapi.service"; -import { Server } from "../shared/models/server.model"; +import { AsyncApiService } from "../../service/asyncapi/asyncapi.service"; +import { Server } from "../../models/server.model"; @Component({ selector: "app-servers", diff --git a/springwolf-ui/src/app/info/info.component.html b/springwolf-ui/src/app/info/info.component.html deleted file mode 100644 index 2a09b2f75..000000000 --- a/springwolf-ui/src/app/info/info.component.html +++ /dev/null @@ -1,8 +0,0 @@ - -

{{ info?.title }}

-
- API VERSION {{ info?.version }} - - - AsyncAPI JSON file -
-

{{ info.description }}

diff --git a/springwolf-ui/src/app/material.module.ts b/springwolf-ui/src/app/material.module.ts index 6fa152909..868d2abec 100644 --- a/springwolf-ui/src/app/material.module.ts +++ b/springwolf-ui/src/app/material.module.ts @@ -12,6 +12,7 @@ import { ClipboardModule } from "@angular/cdk/clipboard"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatSelectModule } from "@angular/material/select"; +import { MatChipsModule } from "@angular/material/chips"; const modules = [ MatButtonModule, @@ -25,6 +26,7 @@ const modules = [ MatSnackBarModule, MatFormFieldModule, MatSelectModule, + MatChipsModule, ]; @NgModule({ diff --git a/springwolf-ui/src/app/shared/models/asyncapi.model.ts b/springwolf-ui/src/app/models/asyncapi.model.ts similarity index 75% rename from springwolf-ui/src/app/shared/models/asyncapi.model.ts rename to springwolf-ui/src/app/models/asyncapi.model.ts index 322f76c4c..23f0971c8 100644 --- a/springwolf-ui/src/app/shared/models/asyncapi.model.ts +++ b/springwolf-ui/src/app/models/asyncapi.model.ts @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: Apache-2.0 */ import { Info } from "./info.model"; import { Server } from "./server.model"; -import { Channel } from "./channel.model"; +import { ChannelOperation } from "./channel.model"; import { Schema } from "./schema.model"; export interface AsyncApi { info: Info; servers: Map; - channels: Channel[]; + channelOperations: ChannelOperation[]; components: { schemas: Map }; } diff --git a/springwolf-ui/src/app/models/bindings.model.ts b/springwolf-ui/src/app/models/bindings.model.ts new file mode 100644 index 000000000..405abaee4 --- /dev/null +++ b/springwolf-ui/src/app/models/bindings.model.ts @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +export interface Bindings { + [protocol: string]: Binding; +} + +export interface Binding { + [protocol: string]: string | Binding; +} diff --git a/springwolf-ui/src/app/models/channel.model.ts b/springwolf-ui/src/app/models/channel.model.ts new file mode 100644 index 000000000..04c797217 --- /dev/null +++ b/springwolf-ui/src/app/models/channel.model.ts @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { Bindings } from "./bindings.model"; +import { Operation } from "./operation.model"; + +export const CHANNEL_ANCHOR_PREFIX = "#channel-"; +export interface ChannelOperation { + name: string; + anchorIdentifier: string; + description?: string; + operation: Operation; + bindings: Bindings; +} diff --git a/springwolf-ui/src/app/shared/models/example.model.ts b/springwolf-ui/src/app/models/example.model.ts similarity index 100% rename from springwolf-ui/src/app/shared/models/example.model.ts rename to springwolf-ui/src/app/models/example.model.ts diff --git a/springwolf-ui/src/app/shared/models/info.model.ts b/springwolf-ui/src/app/models/info.model.ts similarity index 50% rename from springwolf-ui/src/app/shared/models/info.model.ts rename to springwolf-ui/src/app/models/info.model.ts index 4d17cc288..7ac2188ae 100644 --- a/springwolf-ui/src/app/shared/models/info.model.ts +++ b/springwolf-ui/src/app/models/info.model.ts @@ -3,5 +3,16 @@ export interface Info { title: string; version: string; description?: string; + contact: { + url?: string; + email?: { + name: string; + href: string; + }; + }; + license: { + name?: string; + url?: string; + }; asyncApiJson: object; } diff --git a/springwolf-ui/src/app/models/message.model.ts b/springwolf-ui/src/app/models/message.model.ts new file mode 100644 index 000000000..b12c9d8a7 --- /dev/null +++ b/springwolf-ui/src/app/models/message.model.ts @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { Binding } from "./bindings.model"; + +export interface Message { + name: string; + title: string; + description?: string; + payload: { + name: string; + title: string; + anchorUrl: string; + }; + headers: { + name: string; + title: string; + anchorUrl: string; + }; + bindings?: Map; + rawBindings?: { [protocol: string]: object }; +} diff --git a/springwolf-ui/src/app/models/operation.model.ts b/springwolf-ui/src/app/models/operation.model.ts new file mode 100644 index 000000000..a15907543 --- /dev/null +++ b/springwolf-ui/src/app/models/operation.model.ts @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { Message } from "./message.model"; +import { Bindings } from "./bindings.model"; + +export type OperationType = "receive" | "send"; +export interface Operation { + message: Message; + bindings?: Bindings; + protocol?: string; + operationType: OperationType; +} diff --git a/springwolf-ui/src/app/shared/models/schema.model.ts b/springwolf-ui/src/app/models/schema.model.ts similarity index 82% rename from springwolf-ui/src/app/shared/models/schema.model.ts rename to springwolf-ui/src/app/models/schema.model.ts index f854ac8a1..61578a69b 100644 --- a/springwolf-ui/src/app/shared/models/schema.model.ts +++ b/springwolf-ui/src/app/models/schema.model.ts @@ -21,4 +21,8 @@ export interface Schema { required?: string[]; enum?: string[]; example?: Example; + minimum?: number; + maximum?: number; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; } diff --git a/springwolf-ui/src/app/shared/models/server.model.ts b/springwolf-ui/src/app/models/server.model.ts similarity index 84% rename from springwolf-ui/src/app/shared/models/server.model.ts rename to springwolf-ui/src/app/models/server.model.ts index 26556e76c..ff43bb1db 100644 --- a/springwolf-ui/src/app/shared/models/server.model.ts +++ b/springwolf-ui/src/app/models/server.model.ts @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: Apache-2.0 */ export interface Server { - url: string; + host: string; protocol: string; } diff --git a/springwolf-ui/src/app/schemas/schema/schema.component.html b/springwolf-ui/src/app/schemas/schema/schema.component.html deleted file mode 100644 index 4057dd0bd..000000000 --- a/springwolf-ui/src/app/schemas/schema/schema.component.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - -
- {{ property.key }} - * - - - {{ property.value.items.type }}[] - - {{ property.value.items.refTitle }}[] - - - {{ - property.value.type - }} - - {{ property.value.refTitle }} - - ({{ property.value.format }}) -
{{ property.value.description }}
- example: {{ property.value.example.value }} - - {{ - enum - }} - -
diff --git a/springwolf-ui/src/app/servers/servers.component.html b/springwolf-ui/src/app/servers/servers.component.html deleted file mode 100644 index 45615dc6d..000000000 --- a/springwolf-ui/src/app/servers/servers.component.html +++ /dev/null @@ -1,10 +0,0 @@ - -

Servers

- - - {{ server.key }} - - - {{ server.value.url }} - - diff --git a/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts new file mode 100644 index 000000000..1732bca1e --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.ts @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { AsyncApi } from "../../models/asyncapi.model"; +import { Server } from "../../models/server.model"; +import { + ChannelOperation, + CHANNEL_ANCHOR_PREFIX, +} from "../../models/channel.model"; +import { Operation, OperationType } from "src/app/models/operation.model"; +import { Message } from "src/app/models/message.model"; +import { Schema } from "../../models/schema.model"; +import { Injectable } from "@angular/core"; +import { Example } from "../../models/example.model"; +import { Info } from "../../models/info.model"; +import { ServerAsyncApi } from "./models/asyncapi.model"; +import { ServerAsyncApiSchema } from "./models/schema.model"; +import { ServerBinding, ServerBindings } from "./models/bindings.model"; +import { + ServerOperationAction, + ServerOperationMessage, + ServerOperations, +} from "./models/operations.model"; +import { ServerServers } from "./models/servers.model"; +import { ServerChannel, ServerChannels } from "./models/channels.model"; +import { Binding, Bindings } from "src/app/models/bindings.model"; +import { NotificationService } from "../notification.service"; +import { ServerMessages } from "./models/components.model"; + +@Injectable() +export class AsyncApiMapperService { + static BASE_URL = window.location.pathname + window.location.search + "#"; + + constructor(private notificationService: NotificationService) {} + + public toAsyncApi(item: ServerAsyncApi): AsyncApi { + try { + return { + info: this.mapInfo(item), + servers: this.mapServers(item.servers), + channelOperations: this.mapChannelOperations( + item.channels, + item.operations, + item.components.messages + ), + components: { + schemas: this.mapSchemas(item.components.schemas), + }, + }; + } catch (e) { + this.notificationService.showError( + "Error parsing AsyncAPI: " + e.message + ); + return undefined; + } + } + + private mapInfo(item: ServerAsyncApi): Info { + return { + title: item.info.title, + version: item.info.version, + description: item.info.description, + contact: { + url: item.info.contact?.url, + email: item.info.contact?.email && { + name: item.info.contact?.email, + href: "mailto:" + item.info.contact?.email, + }, + }, + license: { + name: item.info.license?.name, + url: item.info.license?.url, + }, + asyncApiJson: item, + }; + } + + private mapServers(servers: ServerServers): Map { + const s = new Map(); + Object.entries(servers).forEach(([k, v]) => s.set(k, v)); + return s; + } + + private mapChannelOperations( + channels: ServerChannels, + operations: ServerOperations, + messages: ServerMessages + ): ChannelOperation[] { + const s = new Array(); + for (let operationsKey in operations) { + this.parsingErrorBoundary("operation " + operationsKey, () => { + const operation = operations[operationsKey]; + const channelName = this.resolveRef(operation.channel.$ref); + + this.verifyBindings(operation.bindings, "operation " + operationsKey); + + const operationMessages: Message[] = this.mapServerAsyncApiMessages( + channelName, + channels[channelName], + messages, + operation.messages + ); + operationMessages.forEach((message) => { + const channelOperation = this.parsingErrorBoundary( + "channel with name " + channelName, + () => + this.mapChannelOperation( + channelName, + channels[channelName], + message, + operation.action, + operation.bindings + ) + ); + + if (channelOperation != undefined) { + s.push(channelOperation); + } + }); + }); + } + return s; + } + + private mapChannelOperation( + channelName: string, + channel: ServerChannel, + message: Message, + operationType: ServerOperationAction, + operationBinding: ServerBindings + ): ChannelOperation { + this.verifyBindings(channel.bindings, "channel " + channelName); + + const operation = this.mapOperation( + operationType, + message, + operationBinding + ); + + return { + name: channelName, + anchorIdentifier: + CHANNEL_ANCHOR_PREFIX + + [ + operation.protocol, + channelName, + operation.operationType, + operation.message.title, + ].join("-"), + description: channel.description, + operation, + bindings: channel.bindings, + }; + } + + private mapServerAsyncApiMessages( + channelName: string, + channel: ServerChannel, + messages: ServerMessages, + operationMessages: ServerOperationMessage[] + ): Message[] { + return operationMessages + .map((operationMessage) => { + return this.parsingErrorBoundary( + "message of channel " + channelName, + () => { + const messageKey = this.resolveRef(operationMessage.$ref); + const channelMessage = channel.messages[messageKey]; + const channelMessageRef = this.resolveRef(channelMessage.$ref); + const message = messages[channelMessageRef]; + + this.verifyBindings(message.bindings, "message " + message.name); + + return { + name: message.name, + title: message.title, + description: message.description, + payload: { + name: message.payload.schema.$ref, + title: this.resolveRef(message.payload.schema.$ref), + anchorUrl: + AsyncApiMapperService.BASE_URL + + this.resolveRef(message.payload.schema.$ref), + }, + headers: { + name: message.headers.$ref, + title: message.headers.$ref?.split("/")?.pop(), + anchorUrl: + AsyncApiMapperService.BASE_URL + + this.resolveRef(message.headers.$ref), + }, + bindings: this.mapServerAsyncApiMessageBindings(message.bindings), + rawBindings: message.bindings, + }; + } + ); + }) + .filter((el) => el != undefined); + } + + private mapServerAsyncApiMessageBindings( + serverMessageBindings?: ServerBindings + ): Map { + const messageBindings = new Map(); + if (serverMessageBindings !== undefined) { + Object.keys(serverMessageBindings).forEach((protocol) => { + messageBindings.set( + protocol, + this.mapServerAsyncApiMessageBinding(serverMessageBindings[protocol]) + ); + }); + } + return messageBindings; + } + + private mapServerAsyncApiMessageBinding( + serverMessageBinding: ServerBinding + ): Binding { + const messageBinding: Binding = {}; + + Object.keys(serverMessageBinding).forEach((key) => { + const value = serverMessageBinding[key]; + if (typeof value === "object") { + messageBinding[key] = this.mapServerAsyncApiMessageBinding(value); + } else { + messageBinding[key] = value; + } + }); + + return messageBinding; + } + + private mapOperation( + operationType: ServerOperationAction, + message: Message, + bindings?: Bindings + ): Operation { + return { + protocol: this.getProtocol(bindings), + operationType: operationType, + message, + bindings, + }; + } + + private getProtocol(bindings?: { + [protocol: string]: object; + }): string | undefined { + if (bindings !== undefined) { + return Object.keys(bindings)[0]; + } + return undefined; + } + + private mapSchemas( + schemas: Map + ): Map { + const s = new Map(); + Object.entries(schemas).forEach(([k, v]) => { + const schema = this.parsingErrorBoundary("schema with name " + k, () => + this.mapSchema(k, v) + ); + + if (schema != undefined) { + s.set(k, schema); + } + }); + return s; + } + + private mapSchema(schemaName: string, schema: ServerAsyncApiSchema): Schema { + const anchorUrl = schema.$ref + ? AsyncApiMapperService.BASE_URL + this.resolveRef(schema.$ref) + : undefined; + const properties = + schema.properties !== undefined + ? this.mapSchemas(schema.properties) + : undefined; + const items = + schema.items !== undefined + ? this.mapSchema(schema.$ref + "[]", schema.items) + : undefined; + const example = + schema.example !== undefined ? new Example(schema.example) : undefined; + return { + name: schemaName, + title: schemaName.split(".")?.pop(), + description: schema.description, + refName: schema.$ref, + refTitle: this.resolveRef(schema.$ref), + anchorIdentifier: "#" + schemaName, + anchorUrl: anchorUrl, + type: schema.type, + items, + format: schema.format, + enum: schema.enum, + properties, + required: schema.required, + example, + minimum: schema.minimum, + maximum: schema.maximum, + exclusiveMinimum: schema.exclusiveMinimum, + exclusiveMaximum: schema.exclusiveMaximum, + }; + } + + private resolveRef(ref: string) { + return ref?.split("/")?.pop(); + } + + private verifyBindings( + bindings: ServerBindings | undefined, + identifier: string + ) { + if (bindings == undefined || Object.keys(bindings).length == 0) { + this.notificationService.showWarning( + "No binding defined for " + identifier + ); + } + } + + private parsingErrorBoundary(path: string, f: () => T): T | undefined { + try { + return f(); + } catch (e) { + this.notificationService.showError( + "Error parsing AsyncAPI " + path + ": " + e.message + ); + return undefined; + } + } +} diff --git a/springwolf-ui/src/app/service/asyncapi/asyncapi.service.ts b/springwolf-ui/src/app/service/asyncapi/asyncapi.service.ts new file mode 100644 index 000000000..39070ea6e --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/asyncapi.service.ts @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { AsyncApi } from "../../models/asyncapi.model"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable, share } from "rxjs"; +import { map } from "rxjs/operators"; +import { EndpointService } from "../endpoint.service"; +import { AsyncApiMapperService } from "./asyncapi-mapper.service"; +import { ServerAsyncApi } from "./models/asyncapi.model"; + +@Injectable() +export class AsyncApiService { + private readonly docs: Observable; + + constructor( + private http: HttpClient, + private asyncApiMapperService: AsyncApiMapperService + ) { + this.docs = this.http.get(EndpointService.docs).pipe( + map((item) => { + return this.asyncApiMapperService.toAsyncApi(item); + }), + share() + ); + } + + public getAsyncApi(): Observable { + return this.docs; + } +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/asyncapi.model.ts b/springwolf-ui/src/app/service/asyncapi/models/asyncapi.model.ts new file mode 100644 index 000000000..e03359534 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/asyncapi.model.ts @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { ServerAsyncApiMessage } from "./message.model"; +import { ServerAsyncApiInfo } from "./info.models"; +import { ServerServers } from "./servers.model"; +import { ServerChannels } from "./channels.model"; +import { ServerComponents } from "./components.model"; +import { ServerOperations } from "./operations.model"; + +export interface ServerAsyncApi { + asyncapi: string; + info: ServerAsyncApiInfo; + servers: ServerServers; + channels: ServerChannels; + operations: ServerOperations; + components: ServerComponents; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/bindings.model.ts b/springwolf-ui/src/app/service/asyncapi/models/bindings.model.ts new file mode 100644 index 000000000..d9506086e --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/bindings.model.ts @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +export interface ServerBindings { + [protocol: string]: ServerBinding; +} + +export interface ServerBinding { + [bindingProperty: string]: string | ServerBinding; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts b/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts new file mode 100644 index 000000000..df52cf0f2 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/channels.model.ts @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { ServerAsyncApiMessage } from "./message.model"; +import { ServerBindings } from "./bindings.model"; + +export interface ServerChannels { + [key: string]: ServerChannel; +} + +export interface ServerChannel { + description?: string; + messages: Map< + string, + { + $ref: string; + } + >; + bindings: ServerBindings; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/components.model.ts b/springwolf-ui/src/app/service/asyncapi/models/components.model.ts new file mode 100644 index 000000000..07b6cb0d4 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/components.model.ts @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { ServerAsyncApiMessage } from "./message.model"; +import { ServerAsyncApiSchema } from "./schema.model"; + +export type ServerMessages = Map; + +export interface ServerComponents { + schemas: Map; + messages: ServerMessages; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/info.models.ts b/springwolf-ui/src/app/service/asyncapi/models/info.models.ts new file mode 100644 index 000000000..f4676af80 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/info.models.ts @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +export interface ServerAsyncApiInfo { + title: string; + version?: string; + description?: string; + contact?: { + name?: string; + url?: string; + email?: string; + }; + license?: { + name?: string; + url?: string; + }; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/message.model.ts b/springwolf-ui/src/app/service/asyncapi/models/message.model.ts new file mode 100644 index 000000000..eab0f11e1 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/message.model.ts @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { ServerBindings } from "./bindings.model"; + +export interface ServerAsyncApiMessage { + name: string; + title: string; + description?: string; + payload: { + schemaFormat: string; + schema: { + $ref: string; + }; + }; + headers: { $ref: string }; + bindings: ServerBindings; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/operations.model.ts b/springwolf-ui/src/app/service/asyncapi/models/operations.model.ts new file mode 100644 index 000000000..bfca4090e --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/operations.model.ts @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { ServerBindings } from "./bindings.model"; + +export interface ServerOperations { + [key: string]: ServerOperation; +} + +export type ServerOperationAction = "receive" | "send"; + +export interface ServerOperation { + action: ServerOperationAction; + channel: { + $ref: string; + }; + messages: { + $ref: string; + }[]; + bindings: ServerBindings; +} + +export interface ServerOperationMessage { + $ref: string; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/schema.model.ts b/springwolf-ui/src/app/service/asyncapi/models/schema.model.ts new file mode 100644 index 000000000..7af6b5acc --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/schema.model.ts @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +export interface ServerAsyncApiSchema { + description?: string; + type: string; + format?: string; + enum?: string[]; + properties: Map; + items?: ServerAsyncApiSchema; + example?: + | { + [key: string]: object; + } + | string; + required?: string[]; + minimum?: number; + maximum?: number; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; + $ref?: string; +} diff --git a/springwolf-ui/src/app/service/asyncapi/models/servers.model.ts b/springwolf-ui/src/app/service/asyncapi/models/servers.model.ts new file mode 100644 index 000000000..a3aab9dc8 --- /dev/null +++ b/springwolf-ui/src/app/service/asyncapi/models/servers.model.ts @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +export interface ServerServers { + [server: string]: { + host: string; + protocol: string; + description?: string; + }; +} diff --git a/springwolf-ui/src/app/shared/endpoints.ts b/springwolf-ui/src/app/service/endpoint.service.ts similarity index 52% rename from springwolf-ui/src/app/shared/endpoints.ts rename to springwolf-ui/src/app/service/endpoint.service.ts index de92fd4e7..8cc193610 100644 --- a/springwolf-ui/src/app/shared/endpoints.ts +++ b/springwolf-ui/src/app/service/endpoint.service.ts @@ -1,15 +1,15 @@ /* SPDX-License-Identifier: Apache-2.0 */ -export class Endpoints { - private static contextPath = Endpoints.getContextPath(); +export class EndpointService { + private static contextPath = EndpointService.getContextPath(); private static getContextPath(): string { let url = document.location.pathname; return url.split("/asyncapi-ui.html")[0]; } - public static docs = Endpoints.contextPath + "/docs"; + public static docs = EndpointService.contextPath + "/docs"; public static getPublishEndpoint(protocol: string): string { - return Endpoints.contextPath + `/${protocol}/publish`; + return EndpointService.contextPath + `/${protocol}/publish`; } } diff --git a/springwolf-ui/src/app/shared/mock/mock-server.ts b/springwolf-ui/src/app/service/mock/mock-server.ts similarity index 100% rename from springwolf-ui/src/app/shared/mock/mock-server.ts rename to springwolf-ui/src/app/service/mock/mock-server.ts diff --git a/springwolf-ui/src/app/service/notification.service.ts b/springwolf-ui/src/app/service/notification.service.ts new file mode 100644 index 000000000..fbe2cac5f --- /dev/null +++ b/springwolf-ui/src/app/service/notification.service.ts @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +import { Injectable } from "@angular/core"; +import { MatSnackBar } from "@angular/material/snack-bar"; + +@Injectable() +export class NotificationService { + constructor(private snackBar: MatSnackBar) {} + + public showError(message: string) { + this.snackBar.open(message, "Close", { verticalPosition: "top" }); + } + + public showWarning(message: string) { + this.snackBar.open(message, "Close", { duration: 3000 }); + } +} diff --git a/springwolf-ui/src/app/shared/publisher.service.ts b/springwolf-ui/src/app/service/publisher.service.ts similarity index 87% rename from springwolf-ui/src/app/shared/publisher.service.ts rename to springwolf-ui/src/app/service/publisher.service.ts index 942edddd4..2273a97bd 100644 --- a/springwolf-ui/src/app/shared/publisher.service.ts +++ b/springwolf-ui/src/app/service/publisher.service.ts @@ -2,7 +2,7 @@ import { Injectable } from "@angular/core"; import { HttpClient, HttpParams } from "@angular/common/http"; import { Observable } from "rxjs"; -import { Endpoints } from "./endpoints"; +import { EndpointService } from "./endpoint.service"; @Injectable() export class PublisherService { @@ -16,7 +16,7 @@ export class PublisherService { headers: object, bindings: object ): Observable { - const url = Endpoints.getPublishEndpoint(protocol); + const url = EndpointService.getPublishEndpoint(protocol); const params = new HttpParams().set("topic", topic); const body = { payload, diff --git a/springwolf-ui/src/app/shared/asyncapi-mapper.service.ts b/springwolf-ui/src/app/shared/asyncapi-mapper.service.ts deleted file mode 100644 index 7f079fc29..000000000 --- a/springwolf-ui/src/app/shared/asyncapi-mapper.service.ts +++ /dev/null @@ -1,289 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -import { AsyncApi } from "./models/asyncapi.model"; -import { Server } from "./models/server.model"; -import { - Channel, - CHANNEL_ANCHOR_PREFIX, - Message, - MessageBinding, - Operation, - OperationType, -} from "./models/channel.model"; -import { Schema } from "./models/schema.model"; -import { Injectable } from "@angular/core"; -import { Example } from "./models/example.model"; -import { Info } from "./models/info.model"; - -interface ServerAsyncApiSchema { - description?: string; - type: string; - format: string; - enum: string[]; - properties?: Map; - items?: ServerAsyncApiSchema; - example?: { - [key: string]: object; - }; - required?: string[]; - $ref?: string; -} - -interface ServerAsyncApiMessage { - name: string; - title: string; - description?: string; - payload: { $ref: string }; - headers: { $ref: string }; - bindings: { [protocol: string]: ServerAsyncApiMessageBinding }; -} -interface ServerAsyncApiMessageBinding { - [protocol: string]: ServerAsyncApiSchema | string; -} - -interface ServerAsyncApiInfo { - title: string; - version: string; - description?: string; -} - -export type ServerAsyncApiChannelMessage = - | ServerAsyncApiMessage - | { oneOf: ServerAsyncApiMessage[] }; -export interface ServerAsyncApi { - asyncapi: string; - info: ServerAsyncApiInfo; - servers: { - [server: string]: { - url: string; - protocol: string; - }; - }; - channels: { - [key: string]: { - description?: string; - subscribe?: { - message: ServerAsyncApiChannelMessage; - bindings?: { [protocol: string]: object }; - }; - publish?: { - message: ServerAsyncApiChannelMessage; - bindings?: { [protocol: string]: object }; - }; - }; - }; - components: { - schemas: Map; - }; -} - -@Injectable() -export class AsyncApiMapperService { - static BASE_URL = window.location.pathname + window.location.search + "#"; - - constructor() {} - - public toAsyncApi(item: ServerAsyncApi): AsyncApi { - return { - info: this.mapInfo(item), - servers: this.mapServers(item.servers), - channels: this.mapChannels(item.channels), - components: { - schemas: this.mapSchemas(item.components.schemas), - }, - }; - } - - private mapInfo(item: ServerAsyncApi): Info { - return { - title: item.info.title, - version: item.info.version, - description: item.info.description, - asyncApiJson: item, - }; - } - - private mapServers(servers: ServerAsyncApi["servers"]): Map { - const s = new Map(); - Object.entries(servers).forEach(([k, v]) => s.set(k, v)); - return s; - } - - private mapChannels(channels: ServerAsyncApi["channels"]): Channel[] { - const s = new Array(); - Object.entries(channels).forEach(([k, v]) => { - const subscriberChannels = this.mapChannel( - k, - v.description, - v.subscribe, - "subscribe" - ); - subscriberChannels.forEach((channel) => s.push(channel)); - - const publisherChannels = this.mapChannel( - k, - v.description, - v.publish, - "publish" - ); - publisherChannels.forEach((channel) => s.push(channel)); - }); - return s; - } - - private mapChannel( - topicName: string, - description: ServerAsyncApi["channels"][""]["description"], - serverOperation: - | ServerAsyncApi["channels"][""]["subscribe"] - | ServerAsyncApi["channels"][""]["publish"], - operationType: OperationType - ): Channel[] { - if (serverOperation !== undefined) { - const messages: Message[] = this.mapMessages(serverOperation.message); - - return messages.map((message) => { - const operation = this.mapOperation( - operationType, - message, - serverOperation.bindings - ); - return { - name: topicName, - anchorIdentifier: - CHANNEL_ANCHOR_PREFIX + - [ - operation.protocol, - topicName, - operation.operation, - operation.message.title, - ].join("-"), - description, - operation, - }; - }); - } - return []; - } - - private mapMessages(message: ServerAsyncApiChannelMessage): Message[] { - if ("oneOf" in message) { - return this.mapServerAsyncApiMessages(message.oneOf); - } - return this.mapServerAsyncApiMessages([message]); - } - - private mapServerAsyncApiMessages( - messages: ServerAsyncApiMessage[] - ): Message[] { - return messages.map((v) => { - return { - name: v.name, - title: v.title, - description: v.description, - payload: { - name: v.payload.$ref, - title: v.payload.$ref?.split(".")?.pop(), - anchorUrl: - AsyncApiMapperService.BASE_URL + v.payload.$ref?.split("/")?.pop(), - }, - headers: { - name: v.headers.$ref, - title: v.headers.$ref?.split("/")?.pop(), - anchorUrl: - AsyncApiMapperService.BASE_URL + v.headers.$ref?.split("/")?.pop(), - }, - bindings: this.mapServerAsyncApiMessageBindings(v.bindings), - rawBindings: v.bindings, - }; - }); - } - - private mapServerAsyncApiMessageBindings(serverMessageBindings?: { - [protocol: string]: ServerAsyncApiMessageBinding; - }): Map { - const messageBindings = new Map(); - if (serverMessageBindings !== undefined) { - Object.keys(serverMessageBindings).forEach((protocol) => { - messageBindings.set( - protocol, - this.mapServerAsyncApiMessageBinding(serverMessageBindings[protocol]) - ); - }); - } - return messageBindings; - } - - private mapServerAsyncApiMessageBinding( - serverMessageBinding: ServerAsyncApiMessageBinding - ): MessageBinding { - const messageBinding: MessageBinding = {}; - - Object.keys(serverMessageBinding).forEach((key) => { - const value = serverMessageBinding[key]; - if (typeof value === "object") { - messageBinding[key] = this.mapSchema("MessageBinding", value); - } else { - messageBinding[key] = value; - } - }); - - return messageBinding; - } - - private mapOperation( - operationType: OperationType, - message: Message, - bindings?: { [protocol: string]: object } - ): Operation { - return { - protocol: this.getProtocol(bindings), - operation: operationType, - message, - bindings, - }; - } - - private getProtocol(bindings?: { [protocol: string]: object }): string { - return Object.keys(bindings)[0]; - } - - private mapSchemas( - schemas: Map - ): Map { - const s = new Map(); - Object.entries(schemas).forEach(([k, v]) => s.set(k, this.mapSchema(k, v))); - return s; - } - - private mapSchema(schemaName: string, schema: ServerAsyncApiSchema): Schema { - const anchorUrl = schema.$ref - ? AsyncApiMapperService.BASE_URL + schema.$ref?.split("/")?.pop() - : undefined; - const properties = - schema.properties !== undefined - ? this.mapSchemas(schema.properties) - : undefined; - const items = - schema.items !== undefined - ? this.mapSchema(schema.$ref + "[]", schema.items) - : undefined; - const example = - schema.example !== undefined ? new Example(schema.example) : undefined; - return { - name: schemaName, - title: schemaName.split(".")?.pop(), - description: schema.description, - refName: schema.$ref, - refTitle: schema.$ref?.split("/")?.pop(), - anchorIdentifier: "#" + schemaName, - anchorUrl: anchorUrl, - type: schema.type, - items, - format: schema.format, - enum: schema.enum, - properties, - required: schema.required, - example, - }; - } -} diff --git a/springwolf-ui/src/app/shared/asyncapi.service.ts b/springwolf-ui/src/app/shared/asyncapi.service.ts deleted file mode 100644 index 4ddefc161..000000000 --- a/springwolf-ui/src/app/shared/asyncapi.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -import { AsyncApi } from "./models/asyncapi.model"; -import { HttpClient } from "@angular/common/http"; -import { Injectable } from "@angular/core"; -import { Observable, of } from "rxjs"; -import { map } from "rxjs/operators"; -import { Endpoints } from "./endpoints"; -import { - AsyncApiMapperService, - ServerAsyncApi, -} from "./asyncapi-mapper.service"; - -@Injectable() -export class AsyncApiService { - private docs: AsyncApi; - - constructor( - private http: HttpClient, - private asyncApiMapperService: AsyncApiMapperService - ) {} - - public getAsyncApi(): Observable { - if (this.docs) { - return of(this.docs); - } - - return this.http.get(Endpoints.docs).pipe( - map((item) => { - this.docs = this.asyncApiMapperService.toAsyncApi(item); - return this.docs; - }) - ); - } -} diff --git a/springwolf-ui/src/app/shared/models/channel.model.ts b/springwolf-ui/src/app/shared/models/channel.model.ts deleted file mode 100644 index 246eca06e..000000000 --- a/springwolf-ui/src/app/shared/models/channel.model.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -import { Schema } from "./schema.model"; - -export const CHANNEL_ANCHOR_PREFIX = "#channel-"; -export interface Channel { - name: string; - anchorIdentifier: string; - description?: string; - operation: Operation; -} - -export type OperationType = "publish" | "subscribe"; -export interface Operation { - message: Message; - bindings?: { [protocol: string]: any }; - protocol: string; - operation: OperationType; -} - -export interface Message { - name: string; - title: string; - description?: string; - payload: { - name: string; - title: string; - anchorUrl: string; - }; - headers: { - name: string; - title: string; - anchorUrl: string; - }; - bindings?: Map; - rawBindings?: { [protocol: string]: object }; -} - -export interface MessageBinding { - [protocol: string]: string | Schema; -} diff --git a/springwolf-ui/src/favicon.ico b/springwolf-ui/src/assets/favicon.ico similarity index 100% rename from springwolf-ui/src/favicon.ico rename to springwolf-ui/src/assets/favicon.ico diff --git a/springwolf-ui/src/asyncapi-ui.html b/springwolf-ui/src/asyncapi-ui.html index ac5457907..e66223e0a 100644 --- a/springwolf-ui/src/asyncapi-ui.html +++ b/springwolf-ui/src/asyncapi-ui.html @@ -5,7 +5,7 @@ Springwolf - +