Skip to content

Commit

Permalink
Different fixes in the asyncapi v3 spec examples (#543)
Browse files Browse the repository at this point in the history
* fix: Minor fix to core spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

* fix: Minor fix to sns-plugin spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

We need to properly configure those values, something that I will take care of in the next PR

* fix: Minor fix to sqs-plugin spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

We need to properly configure those values, something that I will take care of in the next PR

* Fixed styles

* fix: Fixed broken tests

* fix: Added support to sqs-plugin to generate the minimum needed values

To pass the SQS Binding AsyncAPI validation, we need to provide some required fields. This commit adds the minimum values needed.

* fix: Added support to sns-plugin to generate the minimum needed values

To pass the SNS Binding AsyncAPI validation, we need to provide some required fields. This commit adds the minimum values needed.

* fix: Removed small batch of pending FIXMEs

* fix: Removed small batch of pending FIXMEs

* fix: Removed small batch of pending FIXMEs

* fix: Fixed the cloudstream-plugin

The cloudstream-plugin was not generating the operations section of the AsyncAPI spec. Now is fixed and validated.

Multiple FIXMEs were fixed and removed too

* fix: Removed unused methods

* fix: Fixed CloudStream operation action typo

* fix: Removed unneeded channelId field from ChannelObject

* fix: Fixed some code analysis complains

* fix: Fixed Operation messages bug

Whe configuring annotations on the class level, the generated Operations didn't have the proper message references.

This fixes the latest blocking FIXME

* fix: Schema Properties

The Schema Properties should support any kind of value, like booleans.

Making the property accept `Object` seems a more flexible approach.
  • Loading branch information
ctasada authored Jan 18, 2024
1 parent da202c5 commit dd78c5c
Show file tree
Hide file tree
Showing 50 changed files with 741 additions and 286 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class SQSChannelBindingQueue {
* identifier should be the one in this field.
*/
@NotNull
@JsonProperty("queue")
@JsonProperty("name")
private String name;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* SQS Point-To-Point
* </p>
Expand All @@ -32,7 +34,7 @@ public class SQSOperationBinding extends OperationBinding {
*/
@NotNull
@JsonProperty("queues")
private SQSChannelBindingQueue queues;
private List<SQSChannelBindingQueue> queues;

/**
* Optional, defaults to latest. The version of this binding.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class SchemaObject extends ExtendableObject implements Schema {
private String type;

@JsonProperty(value = "properties")
private Map<String, Schema> properties;
private Map<String, Object> properties;

/**
* <a href="https://spec.commonmark.org/">CommonMark syntax</a> can be used for rich text representation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ void shouldCreateSimpleAsyncAPI() throws IOException {
.build();

var channelUserSignedup = ChannelObject.builder()
.channelId("userSignedup")
.address("user/signedup")
.messages(Map.of(userSignUpMessage.getMessageId(), MessageReference.toComponentMessage("UserSignedUp")))
.build();
Expand All @@ -68,12 +67,12 @@ 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))
.channel(ChannelReference.fromChannel("userSignedup"))
.messages(List.of(MessageReference.toChannelMessage(
"userSignedup", userSignUpMessage.getMessageId())))
.build()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public class DefaultChannelsService implements ChannelsService {
private final List<? extends ChannelsScanner> 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<String, ChannelObject> findChannels() {
Expand All @@ -41,7 +41,11 @@ public Map<String, ChannelObject> findChannels() {
return ChannelMerger.mergeChannels(foundChannelItems);
}

// FIXME
/**
* Collects all AsyncAPI Operation using the available {@link ChannelsScanner}
* beans.
* @return Map of operation names mapping to detected Operation
*/
@Override
public Map<String, Operation> findOperations() {
List<Map.Entry<String, Operation>> foundOperations = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,21 @@ public static Map<String, MessageReference> toMessagesMap(Set<MessageObject> mes
return new ArrayList<>(messages.stream().collect(Collectors.toCollection(messageSupplier)))
.stream().collect(Collectors.toMap(MessageObject::getName, MessageReference::toComponentMessage));
}

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

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,
e -> MessageReference.toChannelMessage(channelName, e.getName())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ public class BindingProcessorPriority {
* Examples: Plugins like KafkaOperationBindingProcessor, etc
*/
public static final int PROTOCOL_BINDING = 3;

private BindingProcessorPriority() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ public class ChannelPriority {
* Examples: Plugins like KafkaListener, etc
*/
public static final int AUTO_DISCOVERED = 3;

private ChannelPriority() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

public class AnnotationUtil {

private AnnotationUtil() {}

public static <T extends Annotation> T findAnnotationOrThrow(Class<T> annotationClass, AnnotatedElement element) {
T annotation = findAnnotation(annotationClass, element);
if (annotation == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public Map<String, ChannelObject> scanChannels() {
List<Map.Entry<String, ChannelObject>> channels = classScanner.scan().stream()
.flatMap(this::getAnnotatedMethods)
.map(this::buildChannel)
.filter(this::isInvalidChannel)
.toList();

return ChannelMerger.mergeChannels(channels);
Expand All @@ -75,7 +74,6 @@ public Map<String, Operation> scanOperations() {
List<Map.Entry<String, Operation>> operations = classScanner.scan().stream()
.flatMap(this::getAnnotatedMethods)
.map(this::buildOperation)
.filter(this::isInvalidOperation)
.toList();

return ChannelMerger.mergeOperations(operations);
Expand All @@ -93,44 +91,6 @@ private Stream<MethodAndAnnotation<A>> getAnnotatedMethods(Class<?> type) {
.map(annotation -> new MethodAndAnnotation<>(method, annotation)));
}

private boolean isInvalidChannel(Map.Entry<String, ChannelObject> 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); FIXME

if (!allNonNull) {
log.warn(
"Some data fields are null - method (channel={}) will not be documented: {}",
entry.getKey(),
entry.getValue());
}

return allNonNull;
}

private boolean isInvalidOperation(Map.Entry<String, Operation> 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); FIXME

if (!allNonNull) {
log.warn(
"Some data fields are null - method (channel={}) will not be documented: {}",
entry.getKey(),
entry.getValue());
}

return allNonNull;
}

private Map.Entry<String, ChannelObject> buildChannel(MethodAndAnnotation<A> methodAndAnnotation) {
ChannelObject.ChannelObjectBuilder channelBuilder = ChannelObject.builder();

Expand All @@ -142,7 +102,6 @@ private Map.Entry<String, ChannelObject> buildChannel(MethodAndAnnotation<A> met

List<String> servers = AsyncAnnotationScannerUtil.getServers(operationAnnotation, resolver);
if (servers != null && !servers.isEmpty()) {
// FIXME: It was originally operationId, which doesn't exist anymore
validateServers(servers, operation.getTitle());
channelBuilder.servers(servers.stream()
.map(it -> ServerReference.builder().ref(it).build())
Expand All @@ -157,19 +116,13 @@ private Map.Entry<String, ChannelObject> buildChannel(MethodAndAnnotation<A> met
}

private Map.Entry<String, Operation> buildOperation(MethodAndAnnotation<A> methodAndAnnotation) {
Operation.OperationBuilder operationBuilder = Operation.builder();
AsyncOperation operationAnnotation =
this.asyncAnnotationProvider.getAsyncOperation(methodAndAnnotation.annotation());
String operationName = resolver.resolveStringValue(operationAnnotation.channelName());

Operation operation = buildOperation(operationAnnotation, methodAndAnnotation.method(), operationName);
operation.setAction(this.asyncAnnotationProvider.getOperationType());

MessageObject message = buildMessage(operationAnnotation, methodAndAnnotation.method());

// FIXME
// Operation operation =
// operationBuilder.messages(Map.of(message.getMessageId(), message)).build();
return Map.entry(operationName, operation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,6 @@ public static void processAsyncMessageAnnotation(
messageBuilder.name(annotationName);
}

// FIXME
// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.stream.Stream;

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;

@RequiredArgsConstructor
Expand All @@ -47,6 +48,11 @@ public class ClassLevelAnnotationChannelsScanner<
private final PayloadClassExtractor payloadClassExtractor;
private final SchemasService schemasService;

private enum MessageType {
CHANNEL,
OPERATION;
}

@Override
public Stream<Map.Entry<String, ChannelObject>> processChannels(Class<?> clazz) {
log.debug(
Expand Down Expand Up @@ -97,7 +103,7 @@ private Stream<Map.Entry<String, ChannelObject>> mapClassToChannel(Class<?> comp
}

private Stream<Map.Entry<String, Operation>> mapClassToOperation(Class<?> component) {
log.debug("Mapping class \"{}\" to channels", component.getName());
log.debug("Mapping class \"{}\" to operations", component.getName());

ClassAnnotation classAnnotation = AnnotationUtil.findAnnotationOrThrow(classAnnotationClass, component);

Expand All @@ -106,7 +112,6 @@ private Stream<Map.Entry<String, Operation>> mapClassToOperation(Class<?> compon
return Stream.empty();
}

// FIXME
String channelName = bindingFactory.getChannelName(classAnnotation);
String operationId = channelName + "_receive_" + component.getSimpleName();

Expand All @@ -116,23 +121,28 @@ private Stream<Map.Entry<String, Operation>> mapClassToOperation(Class<?> compon
}

private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Set<Method> methods) {
var messages = buildMessages(classAnnotation, methods);
var messages = buildMessages(classAnnotation, methods, MessageType.CHANNEL);
return buildChannelItem(classAnnotation, messages);
}

private Operation buildOperation(ClassAnnotation classAnnotation, Set<Method> methods) {
var messages = buildMessages(classAnnotation, methods);
var messages = buildMessages(classAnnotation, methods, MessageType.OPERATION);
return buildOperation(classAnnotation, messages);
}

private Map<String, MessageReference> buildMessages(ClassAnnotation classAnnotation, Set<Method> methods) {
private Map<String, MessageReference> buildMessages(
ClassAnnotation classAnnotation, Set<Method> methods, MessageType messageType) {
Set<MessageObject> 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);
}

Expand Down Expand Up @@ -173,13 +183,10 @@ private Operation buildOperation(ClassAnnotation classAnnotation, Map<String, Me
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;
String channelName = bindingFactory.getChannelName(classAnnotation);

// var messageReferences = messages.values().stream().map(m -> MessageReference.fromMessage(m)).toList();

// FIXME
return Operation.builder()
.action(OperationAction.RECEIVE)
.channel(ChannelReference.fromChannel(channelName))
// .messages(messageReferences)
.messages(messages.values().stream().toList())
.bindings(opBinding)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
}
Loading

0 comments on commit dd78c5c

Please sign in to comment.