Skip to content

Commit

Permalink
feat(asyncapi): add field Operation#operationId (#1080)
Browse files Browse the repository at this point in the history
- centralize generation of operationId into OperationIdHelper
 - fix operationId mix-up of receive/send in cloudstream
  • Loading branch information
timonback authored Nov 22, 2024
1 parent c4fdf47 commit 0eaa374
Show file tree
Hide file tree
Showing 32 changed files with 544 additions and 318 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.asyncapi.v3.model.operation;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.github.springwolf.asyncapi.v3.bindings.OperationBinding;
import io.github.springwolf.asyncapi.v3.model.ExtendableObject;
Expand All @@ -10,11 +11,13 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.security_scheme.SecurityScheme;
import jakarta.validation.constraints.NotNull;
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 @@ -30,6 +33,15 @@
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Operation extends ExtendableObject {
/**
* The key represents the operation identifier. The operationId value is case-sensitive. Tools and libraries MAY use
* the operationId value to uniquely identify a operation, therefore, it is RECOMMENDED to follow common programming
* naming conventions.
*/
@JsonIgnore
@Setter(AccessLevel.NONE)
private String operationId;

/**
* Required. Use send when it's expected that the application will send a message to the given channel,
* and receive when the application should expect receiving messages from the given channel.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,13 @@ private void markChannels(AsyncAPI fullAsyncApi, AsyncApiGroup asyncApiGroup, Ma
}

private void markOperationsInChannel(AsyncAPI fullAsyncApi, MarkingContext markingContext, ChannelObject channel) {
fullAsyncApi.getOperations().entrySet().stream()
fullAsyncApi.getOperations().values().stream()
.filter(operationEntry -> matchesOperationInChannel(channel, operationEntry))
.forEach(operationEntry -> markingContext.markedOperationIds.add(operationEntry.getKey()));
.forEach(operationEntry -> markingContext.markedOperationIds.add(operationEntry.getOperationId()));
}

private boolean matchesOperationInChannel(ChannelObject channel, Map.Entry<String, Operation> operationEntry) {
return operationEntry
.getValue()
private boolean matchesOperationInChannel(ChannelObject channel, Operation operation) {
return operation
.getChannel()
.getRef()
.equals(ChannelReference.fromChannel(channel.getChannelId()).getRef());
Expand All @@ -85,14 +84,14 @@ private void markOperations(AsyncAPI fullAsyncApi, AsyncApiGroup asyncApiGroup,
return;
}

fullAsyncApi.getOperations().entrySet().stream()
.filter(operationEntry -> asyncApiGroup.isMatch(operationEntry.getValue()))
.forEach(operationEntry -> {
markingContext.markedOperationIds.add(operationEntry.getKey());
fullAsyncApi.getOperations().values().stream()
.filter(asyncApiGroup::isMatch)
.forEach(operation -> {
markingContext.markedOperationIds.add(operation.getOperationId());

markChannelsForOperation(fullAsyncApi, markingContext, operationEntry.getValue());
markChannelsForOperation(fullAsyncApi, markingContext, operation);

operationEntry.getValue().getMessages().stream()
operation.getMessages().stream()
.map(MessageReference::getRef)
.map(ReferenceUtil::getLastSegment)
.forEach(markingContext.markedComponentMessageIds::add);
Expand Down Expand Up @@ -151,13 +150,23 @@ private void markSchemasInMessageIds(AsyncAPI fullAsyncApi, MarkingContext marki
.filter(message -> markingContext.markedComponentMessageIds.contains(message.getMessageId()))
.toList();

markMessageHeadersForSchemas(messages, schemaIds);
markMessagePayloadsForSchemas(messages, schemaIds);

markSchemas(fullAsyncApi, markingContext, schemaIds);
}

private void markMessageHeadersForSchemas(List<MessageObject> messages, Set<String> schemaIds) {
messages.stream()
.map(MessageObject::getHeaders)
.filter(Objects::nonNull)
.map(MessageHeaders::getReference)
.map(MessageReference::getRef)
.map(ReferenceUtil::getLastSegment)
.forEach(schemaIds::add);
}

private void markMessagePayloadsForSchemas(List<MessageObject> messages, Set<String> schemaIds) {
messages.stream()
.map(MessageObject::getPayload)
.map(MessagePayload::getMultiFormatSchema)
Expand All @@ -168,8 +177,6 @@ private void markSchemasInMessageIds(AsyncAPI fullAsyncApi, MarkingContext marki
.map(MessageReference::getRef)
.map(ReferenceUtil::getLastSegment)
.forEach(schemaIds::add);

markSchemas(fullAsyncApi, markingContext, schemaIds);
}

private void markSchemas(AsyncAPI fullAsyncApi, MarkingContext markingContext, Set<String> schemaIds) {
Expand All @@ -181,22 +188,26 @@ private void markSchemas(AsyncAPI fullAsyncApi, MarkingContext markingContext, S
markingContext.markedComponentSchemaIds.add(schemaEntry.getKey());

if (schemaEntry.getValue().getProperties() != null) {
Set<String> nestedSchemas = schemaEntry.getValue().getProperties().values().stream()
.filter(el -> el instanceof ComponentSchema)
.map(el -> (ComponentSchema) el)
.map(ComponentSchema::getReference)
.filter(Objects::nonNull)
.map(MessageReference::getRef)
.map(ReferenceUtil::getLastSegment)
.filter(schemaId -> !markingContext.markedComponentSchemaIds.contains(schemaId))
.collect(Collectors.toSet());
Set<String> nestedSchemas = findUnmarkedNestedSchemas(markingContext, schemaEntry.getValue());
if (!nestedSchemas.isEmpty()) {
markSchemas(fullAsyncApi, markingContext, nestedSchemas);
}
}
});
}

private static Set<String> findUnmarkedNestedSchemas(MarkingContext markingContext, SchemaObject schema) {
return schema.getProperties().values().stream()
.filter(el -> el instanceof ComponentSchema)
.map(el -> (ComponentSchema) el)
.map(ComponentSchema::getReference)
.filter(Objects::nonNull)
.map(MessageReference::getRef)
.map(ReferenceUtil::getLastSegment)
.filter(schemaId -> !markingContext.markedComponentSchemaIds.contains(schemaId))
.collect(Collectors.toSet());
}

private Map<String, ChannelObject> filterChannels(AsyncAPI fullAsyncApi, MarkingContext markingContext) {
return fullAsyncApi.getChannels().values().stream()
.filter(channel -> markingContext.isChannelMarked(channel.getChannelId()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.util.List;
import java.util.Map;
import java.util.Set;

@RequiredArgsConstructor
@Slf4j
Expand All @@ -35,20 +36,20 @@ public class AsyncAnnotationChannelService<Annotation extends java.lang.annotati
public ChannelObject buildChannel(MethodAndAnnotation<Annotation> methodAndAnnotation) {
AsyncOperation operationAnnotation =
this.asyncAnnotationProvider.getAsyncOperation(methodAndAnnotation.annotation());
String channelName = stringValueResolver.resolveStringValue(operationAnnotation.channelName());
String channelId = ReferenceUtil.toValidId(channelName);

ChannelObject.ChannelObjectBuilder channelBuilder = ChannelObject.builder();
List<String> servers = AsyncAnnotationUtil.getServers(operationAnnotation, stringValueResolver);
if (servers != null && !servers.isEmpty()) {
Operation operation = asyncAnnotationOperationService.buildOperation(
operationAnnotation, methodAndAnnotation.method(), channelId);
operationAnnotation, Set.of(methodAndAnnotation.method()));
validateServers(servers, operation.getTitle());

channelBuilder.servers(
servers.stream().map(ServerReference::fromServer).toList());
}

String channelName = stringValueResolver.resolveStringValue(operationAnnotation.channelName());
String channelId = ReferenceUtil.toValidId(channelName);
MessageObject message =
asyncAnnotationMessageService.buildMessage(operationAnnotation, methodAndAnnotation.method());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
package io.github.springwolf.core.asyncapi.scanners.common.operation;

import io.github.springwolf.asyncapi.v3.bindings.OperationBinding;
import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
Expand All @@ -12,6 +12,7 @@
import io.github.springwolf.core.asyncapi.scanners.common.annotation.AsyncAnnotationUtil;
import io.github.springwolf.core.asyncapi.scanners.common.message.AsyncAnnotationMessageService;
import io.github.springwolf.core.asyncapi.scanners.common.utils.TextUtils;
import io.github.springwolf.core.asyncapi.scanners.operations.OperationIdHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -33,14 +34,10 @@ public class AsyncAnnotationOperationService<Annotation extends java.lang.annota
private final AsyncAnnotationMessageService asyncAnnotationMessageService;
private final StringValueResolver stringValueResolver;

public Operation buildOperation(AsyncOperation asyncOperation, Method method, String channelId) {
MessageObject message = asyncAnnotationMessageService.buildMessage(asyncOperation, method);
List<MessageReference> messages = List.of(MessageReference.toChannelMessage(channelId, message));
public Operation buildOperation(AsyncOperation asyncOperation, Set<Method> methods) {
String channelName = stringValueResolver.resolveStringValue(asyncOperation.channelName());
String channelId = ReferenceUtil.toValidId(channelName);

return buildOperation(asyncOperation, method, channelId, messages);
}

public Operation buildOperation(AsyncOperation asyncOperation, Set<Method> methods, String channelId) {
Method method = methods.stream().findFirst().orElseThrow();
List<MessageReference> messages = methods.stream()
.map(m -> asyncAnnotationMessageService.buildMessage(asyncOperation, m))
Expand All @@ -52,6 +49,9 @@ public Operation buildOperation(AsyncOperation asyncOperation, Set<Method> metho

private Operation buildOperation(
AsyncOperation asyncOperation, Method method, String channelId, List<MessageReference> messages) {
String operationId = OperationIdHelper.buildOperationId(
channelId, this.asyncAnnotationProvider.getOperationType(), method.getName());

String description = this.stringValueResolver.resolveStringValue(asyncOperation.description());
if (StringUtils.isBlank(description)) {
description = "Auto-generated description";
Expand All @@ -67,6 +67,7 @@ private Operation buildOperation(
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;

return Operation.builder()
.operationId(operationId)
.channel(ChannelReference.fromChannel(channelId))
.action(this.asyncAnnotationProvider.getOperationType())
.description(description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.annotation.MethodAndAnnotation;
import io.github.springwolf.core.asyncapi.scanners.common.message.SpringAnnotationMessageService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadSchemaObject;
import io.github.springwolf.core.asyncapi.scanners.operations.OperationIdHelper;
import lombok.RequiredArgsConstructor;

import java.lang.annotation.Annotation;
Expand All @@ -25,13 +27,20 @@ public class SpringAnnotationOperationService<MethodAnnotation extends Annotatio
private final SpringAnnotationMessageService<MethodAnnotation> springAnnotationMessageService;

public Operation buildOperation(
MethodAnnotation annotation, PayloadSchemaObject payloadType, SchemaObject headerSchema) {
MessageObject message = springAnnotationMessageService.buildMessage(annotation, payloadType, headerSchema);
Map<String, OperationBinding> operationBinding = bindingFactory.buildOperationBinding(annotation);
MethodAndAnnotation<MethodAnnotation> annotation,
PayloadSchemaObject payloadType,
SchemaObject headerSchema) {
String channelId = bindingFactory.getChannelId(annotation.annotation());
String operationId = OperationIdHelper.buildOperationId(
channelId, OperationAction.RECEIVE, annotation.method().getName());

MessageObject message =
springAnnotationMessageService.buildMessage(annotation.annotation(), payloadType, headerSchema);
Map<String, OperationBinding> operationBinding = bindingFactory.buildOperationBinding(annotation.annotation());
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;
String channelId = bindingFactory.getChannelId(annotation);

return Operation.builder()
.operationId(operationId)
.action(OperationAction.RECEIVE)
.channel(ChannelReference.fromChannel(channelId))
.messages(List.of(MessageReference.toChannelMessage(channelId, message)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
package io.github.springwolf.core.asyncapi.scanners.common.operation;

import io.github.springwolf.asyncapi.v3.bindings.OperationBinding;
import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.channel.ChannelReference;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.message.SpringAnnotationMessagesService;
import io.github.springwolf.core.asyncapi.scanners.operations.OperationIdHelper;
import lombok.RequiredArgsConstructor;

import java.lang.annotation.Annotation;
Expand All @@ -23,19 +22,19 @@ public class SpringAnnotationOperationsService<ClassAnnotation extends Annotatio
private final BindingFactory<ClassAnnotation> bindingFactory;
private final SpringAnnotationMessagesService<ClassAnnotation> springAnnotationMessagesService;

public Operation buildOperation(ClassAnnotation classAnnotation, Set<Method> methods) {
public Operation buildOperation(ClassAnnotation classAnnotation, Class<?> component, Set<Method> methods) {
var messages = springAnnotationMessagesService.buildMessages(
classAnnotation, methods, SpringAnnotationMessagesService.MessageType.OPERATION);
return buildOperation(classAnnotation, messages);
}

private Operation buildOperation(ClassAnnotation classAnnotation, Map<String, MessageReference> messages) {
Map<String, OperationBinding> operationBinding = bindingFactory.buildOperationBinding(classAnnotation);
Map<String, OperationBinding> opBinding = operationBinding != null ? new HashMap<>(operationBinding) : null;
String channelName = bindingFactory.getChannelName(classAnnotation);
String channelId = ReferenceUtil.toValidId(channelName);

String channelId = bindingFactory.getChannelId(classAnnotation);
String operationId =
OperationIdHelper.buildOperationId(channelId, OperationAction.RECEIVE, component.getSimpleName());

return Operation.builder()
.operationId(operationId)
.action(OperationAction.RECEIVE)
.channel(ChannelReference.fromChannel(channelId))
.messages(messages.values().stream().toList())
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.core.asyncapi.scanners.operations;

import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;

public class OperationIdHelper {
public static String buildOperationId(String channelId, OperationAction operationAction, String componentName) {
return String.join("_", channelId, operationAction.type, componentName);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.operations.annotations;

import io.github.springwolf.asyncapi.v3.model.ReferenceUtil;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationProvider;
Expand All @@ -13,7 +12,6 @@
import io.github.springwolf.core.asyncapi.scanners.operations.OperationsInClassScanner;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -55,17 +53,11 @@ private Stream<Map.Entry<String, Operation>> mapClassToOperation(
ClassAnnotation classAnnotation = AnnotationUtil.findFirstAnnotationOrThrow(classAnnotationClass, component);
AsyncOperation asyncOperation = asyncAnnotationProvider.getAsyncOperation(classAnnotation);

String channelName =
asyncAnnotationProvider.getAsyncOperation(classAnnotation).channelName();
String channelId = ReferenceUtil.toValidId(channelName);
String operationId = StringUtils.joinWith(
"_", channelId, asyncAnnotationProvider.getOperationType().type, component.getSimpleName());

Set<Method> methods =
annotatedMethods.stream().map(MethodAndAnnotation::method).collect(Collectors.toSet());
Operation operation = asyncAnnotationOperationsService.buildOperation(asyncOperation, methods, channelId);
Operation operation = asyncAnnotationOperationsService.buildOperation(asyncOperation, methods);
annotatedMethods.forEach(
method -> customizers.forEach(customizer -> customizer.customize(operation, method.method())));
return Stream.of(Map.entry(operationId, operation));
return Stream.of(Map.entry(operation.getOperationId(), operation));
}
}
Loading

0 comments on commit 0eaa374

Please sign in to comment.