Skip to content

Commit

Permalink
feat(core): add ChannelObject#channelId and populate ChannelObject#ad…
Browse files Browse the repository at this point in the history
…dress (#797)

* feat(core): add ChannelObject#channelId and populate ChannelObject#address

feat(core): always create valid operationId
feat(core): populate Channel#address
refactor(core): move ServerReference to server package
feat(core): hide channelId and messageId setter
feat(core): remove forbidden channelId character /
feat(core): add ChannelObject#channelId
  • Loading branch information
timonback authored Jun 21, 2024
1 parent 75ff837 commit 07e38d9
Show file tree
Hide file tree
Showing 36 changed files with 274 additions and 143 deletions.
1 change: 1 addition & 0 deletions springwolf-asyncapi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
testImplementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonVersion}"
testImplementation "io.swagger.core.v3:swagger-core-jakarta:${swaggerVersion}"
testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${jsonUnitAssertJVersion}"
testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"

testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.asyncapi.v3.model;

public class ReferenceUtil {
private static final String FORBIDDEN_ID_CHARACTER = "/";

public static String toValidId(String name) {
return name.replaceAll(FORBIDDEN_ID_CHARACTER, "_");
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.asyncapi.v3.model.channel;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding;
import io.github.springwolf.asyncapi.v3.model.ExtendableObject;
import io.github.springwolf.asyncapi.v3.model.ExternalDocumentation;
import io.github.springwolf.asyncapi.v3.model.Tag;
import io.github.springwolf.asyncapi.v3.model.channel.message.Message;
import io.github.springwolf.asyncapi.v3.model.server.ServerReference;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
import java.util.Map;
Expand All @@ -27,6 +31,15 @@
@AllArgsConstructor
@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
@Setter(AccessLevel.NONE)
private String channelId;

/**
* An optional string representation of this channel's address. The address is typically the "topic name",
Expand Down Expand Up @@ -102,4 +115,14 @@ public class ChannelObject extends ExtendableObject implements Channel {
*/
@JsonProperty(value = "bindings")
private Map<String, ChannelBinding> bindings;

/*
* Override the getChannelId to guarantee that there's always a value. Defaults to 'address'
*/
public String getChannelId() {
if (channelId == null) {
return this.address;
}
return channelId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public String getRef() {
return ref;
}

public static ChannelReference fromChannel(String channelName) {
return new ChannelReference("#/channels/" + channelName);
public static ChannelReference fromChannel(ChannelObject channel) {
return new ChannelReference("#/channels/" + channel.getChannelId());
}

public static ChannelReference fromChannel(String channelId) {
return new ChannelReference("#/channels/" + channelId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import io.github.springwolf.asyncapi.v3.model.ExternalDocumentation;
import io.github.springwolf.asyncapi.v3.model.Tag;
import io.github.springwolf.asyncapi.v3.model.channel.CorrelationID;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
import java.util.Map;
Expand All @@ -33,6 +35,7 @@ public class MessageObject extends ExtendableObject implements Message {
* naming conventions.
*/
@JsonIgnore
@Setter(AccessLevel.NONE)
private String messageId;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ public static MessageReference toComponentMessage(String messageId) {
return new MessageReference("#/components/messages/" + messageId);
}

public static MessageReference toChannelMessage(String channelName, MessageObject message) {
return new MessageReference("#/channels/" + channelName + "/messages/" + message.getMessageId());
public static MessageReference toChannelMessage(String channelId, MessageObject message) {
return new MessageReference("#/channels/" + channelId + "/messages/" + message.getMessageId());
}

public static MessageReference toChannelMessage(String channelName, String messageId) {
return new MessageReference("#/channels/" + channelName + "/messages/" + messageId);
public static MessageReference toChannelMessage(String channelId, String messageId) {
return new MessageReference("#/channels/" + channelId + "/messages/" + messageId);
}

public static MessageReference toSchema(String schemaName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.asyncapi.v3.model.channel;
package io.github.springwolf.asyncapi.v3.model.server;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.springwolf.asyncapi.v3.model.Reference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.github.springwolf.asyncapi.v3.model.AsyncAPI;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
import io.github.springwolf.asyncapi.v3.model.channel.ServerReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.Message;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessagePayload;
Expand All @@ -23,6 +22,7 @@
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import io.github.springwolf.asyncapi.v3.model.server.Server;
import io.github.springwolf.asyncapi.v3.model.server.ServerReference;
import org.junit.jupiter.api.Test;

import java.io.IOException;
Expand Down Expand Up @@ -110,7 +110,7 @@ private AsyncAPI getAsyncAPITestObject() {
.info(info)
.defaultContentType("application/json")
.servers(Map.of("production", productionServer))
.channels(Map.of("new-user", newUserChannel))
.channels(Map.of(newUserChannel.getChannelId(), newUserChannel))
.components(
Components.builder().schemas(schemas).messages(messages).build())
.operations(Map.of("new-user_listenerMethod_subscribe", newUserOperation))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ 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 @@ -67,7 +68,7 @@ void shouldCreateSimpleAsyncAPI() throws IOException {
.version("1.0.0")
.description("This service is in charge of processing user signups")
.build())
.channels(Map.of("userSignedup", channelUserSignedup))
.channels(Map.of(channelUserSignedup.getChannelId(), channelUserSignedup))
.operations(Map.of(
"sendUserSignedup",
Operation.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.asyncapi.v3.model;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class ReferenceUtilTest {
@Test
void shouldCorrectIllegalCharacter() {
String name = "users/{userId}";

assertThat(ReferenceUtil.toValidId(name)).isEqualTo("users_{userId}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.github.springwolf.asyncapi.v3.model.ExternalDocumentation;
import io.github.springwolf.asyncapi.v3.model.Tag;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.server.ServerReference;
import org.junit.jupiter.api.Test;

import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.channels;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject;
import io.github.springwolf.asyncapi.v3.model.channel.ServerReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.server.Server;
import io.github.springwolf.asyncapi.v3.model.server.ServerReference;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.scanners.ChannelsScanner;
Expand Down Expand Up @@ -64,8 +65,9 @@ private Map.Entry<String, ChannelObject> buildChannel(MethodAndAnnotation<A> met
AsyncOperation operationAnnotation =
this.asyncAnnotationProvider.getAsyncOperation(methodAndAnnotation.annotation());
String channelName = resolver.resolveStringValue(operationAnnotation.channelName());
String channelId = ReferenceUtil.toValidId(channelName);

Operation operation = buildOperation(operationAnnotation, methodAndAnnotation.method(), channelName);
Operation operation = buildOperation(operationAnnotation, methodAndAnnotation.method(), channelId);

List<String> servers = AsyncAnnotationUtil.getServers(operationAnnotation, resolver);
if (servers != null && !servers.isEmpty()) {
Expand All @@ -76,9 +78,11 @@ private Map.Entry<String, ChannelObject> buildChannel(MethodAndAnnotation<A> met
MessageObject message = buildMessage(operationAnnotation, methodAndAnnotation.method());

ChannelObject channelItem = channelBuilder
.channelId(channelId)
.address(channelName)
.messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message)))
.build();
return Map.entry(channelName, channelItem);
return Map.entry(channelItem.getChannelId(), channelItem);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private ChannelMerger() {}
* Messages within channels are merged
*
* @param channelEntries Ordered pairs of channel name to Channel
* @return A map of channelName to a single Channel
* @return A map of channelId to a single Channel
*/
public static Map<String, ChannelObject> mergeChannels(List<Map.Entry<String, ChannelObject>> channelEntries) {
Map<String, ChannelObject> mergedChannels = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package io.github.springwolf.core.asyncapi.scanners.channels.annotations;

import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding;
import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
Expand Down Expand Up @@ -62,11 +63,8 @@ private Stream<Map.Entry<String, ChannelObject>> mapClassToChannel(Class<?> comp
return Stream.empty();
}

String channelName = bindingFactory.getChannelName(classAnnotation);

ChannelObject channelItem = buildChannelItem(classAnnotation, annotatedMethods);

return Stream.of(Map.entry(channelName, channelItem));
return Stream.of(Map.entry(channelItem.getChannelId(), channelItem));
}

private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Set<Method> methods) {
Expand All @@ -77,7 +75,10 @@ private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Set<Meth
private ChannelObject buildChannelItem(ClassAnnotation classAnnotation, Map<String, MessageReference> messages) {
Map<String, ChannelBinding> channelBinding = bindingFactory.buildChannelBinding(classAnnotation);
Map<String, ChannelBinding> chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null;
String channelName = bindingFactory.getChannelName(classAnnotation);
return ChannelObject.builder()
.channelId(ReferenceUtil.toValidId(channelName))
.address(channelName)
.bindings(chBinding)
.messages(new HashMap<>(messages))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package io.github.springwolf.core.asyncapi.scanners.channels.annotations;

import io.github.springwolf.asyncapi.v3.bindings.ChannelBinding;
import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
Expand Down Expand Up @@ -63,11 +64,10 @@ private Map.Entry<String, ChannelObject> mapMethodToChannel(Method method) {
MethodAnnotation annotation = AnnotationScannerUtil.findAnnotationOrThrow(methodAnnotationClass, method);

NamedSchemaObject payloadSchema = payloadMethodService.extractSchema(method);

SchemaObject headerSchema = headerClassExtractor.extractHeader(method, payloadSchema);
ChannelObject channelItem = buildChannelItem(annotation, payloadSchema, headerSchema);

String channelName = bindingFactory.getChannelName(annotation);
return Map.entry(channelName, channelItem);
return Map.entry(channelItem.getChannelId(), channelItem);
}

private ChannelObject buildChannelItem(
Expand All @@ -79,7 +79,10 @@ private ChannelObject buildChannelItem(
private ChannelObject buildChannelItem(MethodAnnotation annotation, MessageObject message) {
Map<String, ChannelBinding> channelBinding = bindingFactory.buildChannelBinding(annotation);
Map<String, ChannelBinding> chBinding = channelBinding != null ? new HashMap<>(channelBinding) : null;
String channelName = bindingFactory.getChannelName(annotation);
return ChannelObject.builder()
.channelId(ReferenceUtil.toValidId(channelName))
.address(channelName)
.messages(Map.of(message.getMessageId(), MessageReference.toComponentMessage(message)))
.bindings(chBinding)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import io.github.springwolf.core.asyncapi.scanners.common.utils.TextUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;

import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -65,26 +65,27 @@ protected Stream<MethodAndAnnotation<A>> getAnnotatedMethods(Class<?> type) {
.map(annotation -> new MethodAndAnnotation<>(method, annotation)));
}

protected Operation buildOperation(AsyncOperation asyncOperation, Method method, String channelName) {
protected Operation buildOperation(AsyncOperation asyncOperation, Method method, String channelId) {
String description = this.resolver.resolveStringValue(asyncOperation.description());
if (!StringUtils.hasText(description)) {
if (StringUtils.isBlank(description)) {
description = "Auto-generated description";
} else {
description = TextUtils.trimIndent(description);
}

String operationTitle = channelName + "_" + this.asyncAnnotationProvider.getOperationType().type;
String operationTitle =
StringUtils.joinWith("_", channelId, this.asyncAnnotationProvider.getOperationType().type);

Map<String, OperationBinding> operationBinding =
AsyncAnnotationUtil.processOperationBindingFromAnnotation(method, operationBindingProcessors);
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;
MessageObject message = buildMessage(asyncOperation, method);

return Operation.builder()
.channel(ChannelReference.fromChannel(channelName))
.channel(ChannelReference.fromChannel(channelId))
.description(description)
.title(operationTitle)
.messages(List.of(MessageReference.toChannelMessage(channelName, message)))
.messages(List.of(MessageReference.toChannelMessage(channelId, message)))
.bindings(opBinding)
.build();
}
Expand All @@ -103,10 +104,10 @@ protected MessageObject buildMessage(AsyncOperation operationData, Method method
.build());

String description = operationData.message().description();
if (!StringUtils.hasText(description) && payloadSchema.schema() != null) {
if (StringUtils.isBlank(description) && payloadSchema.schema() != null) {
description = payloadSchema.schema().getDescription();
}
if (StringUtils.hasText(description)) {
if (StringUtils.isNotBlank(description)) {
description = this.resolver.resolveStringValue(description);
description = TextUtils.trimIndent(description);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -44,6 +45,7 @@ public static Map<String, MessageReference> toOperationsMessagesMap(
.stream()
.collect(Collectors.toMap(
MessageObject::getMessageId,
e -> MessageReference.toChannelMessage(channelName, e.getMessageId())));
e -> MessageReference.toChannelMessage(
ReferenceUtil.toValidId(channelName), e.getMessageId())));
}
}
Loading

0 comments on commit 07e38d9

Please sign in to comment.