Skip to content

Commit

Permalink
feat(core): integrate grouping into DefaultAsyncApiService
Browse files Browse the repository at this point in the history
Co-authored-by: Timon Back <[email protected]>
  • Loading branch information
sam0r040 and timonback committed Oct 18, 2024
1 parent 3692d0d commit a99540e
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
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.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.core.configuration.docket.AsyncApiGroup;
import lombok.AllArgsConstructor;
import lombok.val;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -21,18 +20,19 @@ public class AsyncApiGroupingService {
// private final String group = "admin";

// if empty, keep all
private final List<OperationAction> operationActionsToKeep;
private final List<String> channelNamesToKeep; // TODO: currently Ids, change to name

// TODO: Context class

// TODO: Context class/object
// private final Map<String, Boolean> markedChannelIds;
private final Set<String> markedOperationIds = new HashSet<>();
private final Set<String> markedChannelIds = new HashSet<>();
// private final Map<String, Boolean> markedComponentIds;

public AsyncAPI groupAPI(AsyncAPI fullAsyncApi) {
markOperations(fullAsyncApi);
markChannels(fullAsyncApi);
public AsyncAPI groupAPI(AsyncAPI fullAsyncApi, AsyncApiGroup asyncApiGroup) {
Boolean markEverything = asyncApiGroup.getOperationActionsToKeep().isEmpty() && asyncApiGroup.getChannelNamesToKeep().isEmpty();

markOperations(fullAsyncApi, asyncApiGroup, markEverything);
markChannels(fullAsyncApi, asyncApiGroup);

AsyncAPI asyncAPI = AsyncAPI.builder()
.info(fullAsyncApi.getInfo())
Expand All @@ -57,13 +57,13 @@ private Map<String, ChannelObject> filterChannels(AsyncAPI fullAsyncApi) {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

private void markChannels(AsyncAPI fullAsyncApi) {
private void markChannels(AsyncAPI fullAsyncApi, AsyncApiGroup asyncApiGroup) {
if (fullAsyncApi.getChannels() == null) {
return;
}

fullAsyncApi.getChannels().entrySet().stream()
.filter(entry -> channelNamesToKeep.contains(entry.getKey()))
.filter(entry -> asyncApiGroup.getChannelNamesToKeep().contains(entry.getKey()))
.forEach(entry -> {
markedChannelIds.add(entry.getKey());

Expand All @@ -82,14 +82,14 @@ private void markChannels(AsyncAPI fullAsyncApi) {
});
}

private void markOperations(AsyncAPI fullAsyncApi) {
private void markOperations(AsyncAPI fullAsyncApi, AsyncApiGroup asyncApiGroup, Boolean markEverything) {
if (fullAsyncApi.getOperations() == null) {
return;
}

fullAsyncApi.getOperations().entrySet().stream()
.filter(entry ->
operationActionsToKeep.contains(entry.getValue().getAction()))
markEverything || asyncApiGroup.getOperationActionsToKeep().contains(entry.getValue().getAction()))
.forEach(entry -> {
markedOperationIds.add(entry.getKey());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@

import io.github.springwolf.asyncapi.v3.model.AsyncAPI;

import java.util.Optional;

public interface AsyncApiService {

AsyncAPI getAsyncAPI();

// TODO where do we want do add the breaking change?
default Optional<AsyncAPI> getForGroupName(String groupName) {
return Optional.ofNullable(getAsyncAPI());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
import io.github.springwolf.core.asyncapi.operations.OperationsService;
import io.github.springwolf.core.configuration.docket.AsyncApiDocket;
import io.github.springwolf.core.configuration.docket.AsyncApiDocketService;
import io.github.springwolf.core.configuration.docket.AsyncApiGroup;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
Expand All @@ -26,7 +30,8 @@ public class DefaultAsyncApiService implements AsyncApiService {
* @param asyncAPI
* @param exception
*/
private record AsyncAPIResult(AsyncAPI asyncAPI, Throwable exception) {}
private record AsyncAPIResult(AsyncAPI asyncAPI, Throwable exception) {
}
// -> master (internal)
// -> per group

Expand All @@ -35,8 +40,11 @@ private record AsyncAPIResult(AsyncAPI asyncAPI, Throwable exception) {}
private final OperationsService operationsService;
private final ComponentsService componentsService;
private final List<AsyncApiCustomizer> customizers;
private final List<AsyncApiGroup> apiGroups;
AsyncApiGroupingService groupingService = new AsyncApiGroupingService();

private volatile AsyncAPIResult asyncAPIResult = null;
private volatile Map<String, AsyncAPI> asyncApiGroupMap = new HashMap<>();

@Override
public AsyncAPI getAsyncAPI() {
Expand All @@ -51,6 +59,11 @@ public AsyncAPI getAsyncAPI() {
}
}

@Override
public Optional<AsyncAPI> getForGroupName(String groupName) {
return Optional.ofNullable(this.asyncApiGroupMap.get(groupName));
}

/**
* Does the 'heavy work' of building the AsyncAPI documents once. Stores the resulting
* AsyncAPI document or alternatively a caught exception/error in the instance variable asyncAPIResult.
Expand Down Expand Up @@ -95,6 +108,9 @@ protected synchronized void initAsyncAPI() {
}
this.asyncAPIResult = new AsyncAPIResult(asyncAPI, null);


this.asyncApiGroupMap = apiGroups.stream().map(asyncApiGroup -> Map.entry(asyncApiGroup.getGroupName(), groupingService.groupAPI(asyncAPIResult.asyncAPI(), asyncApiGroup))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

log.debug("AsyncAPI document was built");
} catch (Throwable t) {
log.debug("Failed to build AsyncAPI document", t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaUtil;
import io.github.springwolf.core.asyncapi.schemas.converters.SchemaTitleModelConverter;
import io.github.springwolf.core.configuration.docket.AsyncApiDocketService;
import io.github.springwolf.core.configuration.docket.AsyncApiGroup;
import io.github.springwolf.core.configuration.docket.DefaultAsyncApiDocketService;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
Expand Down Expand Up @@ -82,9 +83,10 @@ public AsyncApiService asyncApiService(
ChannelsService channelsService,
OperationsService operationsService,
ComponentsService componentsService,
List<AsyncApiCustomizer> customizers) {
List<AsyncApiCustomizer> customizers,
List<AsyncApiGroup> asyncApiGroups) {
return new DefaultAsyncApiService(
asyncApiDocketService, channelsService, operationsService, componentsService, customizers);
asyncApiDocketService, channelsService, operationsService, componentsService, customizers, asyncApiGroups);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.configuration.docket;

import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@AllArgsConstructor
@Getter
@Builder
public class AsyncApiGroup {
private final String group;
private final String title;
private final String groupName;

private final List<OperationAction> operationActionsToKeep;
private final List<String> channelNamesToKeep; // TODO: currently Ids, change to name

// Implementation Roadmap
// first draft/beta:
// * option to group by a single aspect i.e. only by Operation Option OR by ChannelName OR Message/Payload-Prefix (v1.Message, v2.Message)
// * different groups selectable via ui
// * Options are exclusive -> validation/undefined behavior
// * configuration via properties no code/no beans


//private final String title;

// public boolean match(Operation operation,
// AsyncAPI fullApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import io.github.springwolf.asyncapi.v3.model.info.Info;
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.core.configuration.docket.AsyncApiGroup;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.List;
Expand All @@ -21,8 +23,13 @@ void shouldCreateNewAsyncApi() {
// given
AsyncAPI full = AsyncAPI.builder().build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped).isNotSameAs(full);
Expand All @@ -34,8 +41,13 @@ void shouldUseIdenticalInfo() {
Info info = Info.builder().title("title").build();
AsyncAPI full = AsyncAPI.builder().info(info).build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getInfo()).isSameAs(info);
Expand All @@ -46,8 +58,13 @@ void shouldUseIdenticalId() {
// given
AsyncAPI full = AsyncAPI.builder().id("id").build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getId()).isEqualTo("id");
Expand All @@ -59,8 +76,13 @@ void shouldUseIdenticalDefaultContentType() {
AsyncAPI full =
AsyncAPI.builder().defaultContentType("application/json").build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getDefaultContentType()).isEqualTo("application/json");
Expand All @@ -86,16 +108,21 @@ void shouldFilterOperationByAction() {
.operations(Map.of("send", sendOperation, "receive", receiveOperation))
.build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of(OperationAction.SEND))
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(OperationAction.SEND), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getChannels()).isEqualTo(Map.of(channel.getChannelId(), channel));
assertThat(grouped.getOperations()).isEqualTo(Map.of("send", sendOperation));
}

@Test
// should get all copied for all empty filters
// should get all copied for all empty filters
void shouldGetAllOperationsWhenActionsIsEmpty() {
// given
String channelId = "channelId";
Expand All @@ -115,8 +142,13 @@ void shouldGetAllOperationsWhenActionsIsEmpty() {
.operations(Map.of("send", sendOperation, "receive", receiveOperation))
.build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
// TODO: special case: when nothing is marked, mark everything
Expand Down Expand Up @@ -146,8 +178,13 @@ void shouldFilterOperationByActionAndTransitiveChannels() {
.operations(Map.of("send", sendOperation, "receive", receiveOperation))
.build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of(OperationAction.SEND))
.channelNamesToKeep(List.of())
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(OperationAction.SEND), List.of()).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getChannels()).isEqualTo(Map.of(channel1.getChannelId(), channel1));
Expand Down Expand Up @@ -176,11 +213,68 @@ void shouldFilterOperationsByChannelName() {
.operations(Map.of("send", sendOperation, "receive", receiveOperation))
.build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of())
.channelNamesToKeep(List.of(channel1.getChannelId()))
.build();

// when
AsyncAPI grouped = new AsyncApiGroupingService(List.of(), List.of(channel1.getChannelId())).groupAPI(full);
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getOperations()).isEqualTo(Map.of("send", sendOperation));
assertThat(grouped.getChannels()).isEqualTo(Map.of(channel1.getChannelId(), channel1));
}

@Test
@Disabled
void testOrIsNotEnough() {
// given
String channelId1 = "channelId1";
ChannelObject channel1 = ChannelObject.builder().channelId(channelId1).build();
String channelId2 = "channelId2";
ChannelObject channel2 = ChannelObject.builder().channelId(channelId2).build();

Operation sendOperation1 = Operation.builder()
.action(OperationAction.SEND)
.channel(ChannelReference.fromChannel(channel1))
.build();
Operation receiveOperation1 = Operation.builder()
.action(OperationAction.RECEIVE)
.channel(ChannelReference.fromChannel(channelId1))
.build();

Operation sendOperation2 = Operation.builder()
.action(OperationAction.SEND)
.channel(ChannelReference.fromChannel(channel2))
.build();
Operation receiveOperation2 = Operation.builder()
.action(OperationAction.RECEIVE)
.channel(ChannelReference.fromChannel(channelId2))
.build();

AsyncAPI full = AsyncAPI.builder()
.channels(Map.of(channel1.getChannelId(), channel1, channel2.getChannelId(), channel2))
.operations(Map.of(
"channel1_send", sendOperation1,
"channel1_receive", receiveOperation1,
"channel2_send", sendOperation2,
"channel2_receive", receiveOperation2
)
)
.build();

AsyncApiGroup asyncApiGroup = AsyncApiGroup.builder()
.operationActionsToKeep(List.of(OperationAction.SEND))
.channelNamesToKeep(List.of(channel1.getChannelId()))
.build();


// when
AsyncAPI grouped = new AsyncApiGroupingService().groupAPI(full, asyncApiGroup);

// then
assertThat(grouped.getOperations()).isEqualTo(Map.of("send", sendOperation1));
assertThat(grouped.getChannels()).isEqualTo(Map.of(channel1.getChannelId(), channel1));
}
}
Loading

0 comments on commit a99540e

Please sign in to comment.