Skip to content

Commit

Permalink
Feat/create new header schema when modified (#941)
Browse files Browse the repository at this point in the history
* test: reproduce GH-938 (wip)

* feat(core): create new header schema when properties from multiple schemas are merged GH-938

Co-authored-by: Timon Back <[email protected]>

---------

Co-authored-by: David Müller <[email protected]>
  • Loading branch information
timonback and sam0r040 authored Aug 23, 2024
1 parent 60414e0 commit 67adc08
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersBuilder;
import io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderClassExtractor;
import io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderSchemaObjectMerger;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadMethodService;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadSchemaObject;
import io.github.springwolf.core.asyncapi.scanners.common.utils.AnnotationScannerUtil;
import io.github.springwolf.core.asyncapi.scanners.operations.annotations.SpringAnnotationClassLevelOperationsScanner;
import io.github.springwolf.core.asyncapi.schemas.SchemaObjectMerger;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -97,7 +97,7 @@ protected Map<String, MessageReference> buildMessages(
protected MessageObject buildMessage(
ClassAnnotation classAnnotation, PayloadSchemaObject payloadSchema, SchemaObject headers) {
SchemaObject headerSchema = asyncHeadersBuilder.buildHeaders(payloadSchema);
SchemaObject mergedHeaderSchema = SchemaObjectMerger.merge(headerSchema, headers);
SchemaObject mergedHeaderSchema = HeaderSchemaObjectMerger.merge(headerSchema, headers);
String headerSchemaName = componentsService.registerSchema(mergedHeaderSchema);

Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(classAnnotation, headerSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersBuilder;
import io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderSchemaObjectMerger;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadSchemaObject;
import io.github.springwolf.core.asyncapi.schemas.SchemaObjectMerger;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand All @@ -31,7 +31,7 @@ public abstract class MethodLevelAnnotationScanner<MethodAnnotation extends Anno
protected MessageObject buildMessage(
MethodAnnotation annotation, PayloadSchemaObject payloadSchema, SchemaObject headers) {
SchemaObject headerSchema = asyncHeadersBuilder.buildHeaders(payloadSchema);
SchemaObject mergedHeaderSchema = SchemaObjectMerger.merge(headerSchema, headers);
SchemaObject mergedHeaderSchema = HeaderSchemaObjectMerger.merge(headerSchema, headers);
String headerModelName = componentsService.registerSchema(mergedHeaderSchema);

Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(annotation, mergedHeaderSchema);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common.headers;

import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class HeaderSchemaObjectMerger {

public static SchemaObject merge(SchemaObject initial, SchemaObject... schemas) {

int additionalProperties = Arrays.stream(schemas)
.filter(schemaObject -> schemaObject != null && schemaObject.getProperties() != null)
.mapToInt(schema -> schema.getProperties().size())
.sum();
if (additionalProperties == 0) {
return initial;
}

SchemaObject.SchemaObjectBuilder headerSchemaBuilder =
SchemaObject.builder().type(SchemaType.OBJECT);

String title = initial.getTitle();
String description = initial.getDescription();
Map<String, Object> headerProperties = new HashMap<>(initial.getProperties());

for (SchemaObject schema : schemas) {
if (schema == null) {
continue;
}

if (StringUtils.isBlank(description)) {
description = schema.getDescription();
}

schema.getProperties().forEach(headerProperties::putIfAbsent);
}

return headerSchemaBuilder
.title(generateTitle(initial, headerProperties))
.description(description)
.properties(headerProperties)
.build();
}

public static String generateHeaderSchemaName(Object object) {
return generateHeaderSchemaName("Headers", object);
}

private static String generateHeaderSchemaName(String prefix, Object object) {
return prefix + "-" + Math.abs(object.hashCode());
}

private static String generateTitle(SchemaObject initial, Map<String, Object> headerProperties) {
if (StringUtils.isBlank(initial.getTitle())) {
return generateHeaderSchemaName(headerProperties);
} else {
return generateHeaderSchemaName(initial.getTitle(), headerProperties.hashCode());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.Optional;
import java.util.stream.Collectors;

import static io.github.springwolf.core.asyncapi.scanners.common.headers.HeaderSchemaObjectMerger.generateHeaderSchemaName;
import static java.util.stream.Collectors.groupingBy;

public class AsyncAnnotationUtil {
Expand All @@ -44,8 +45,9 @@ public static SchemaObject getAsyncHeaders(AsyncOperation op, StringValueResolve
return AsyncHeadersNotDocumented.NOT_DOCUMENTED;
}

String headerSchemaTitle =
StringUtils.hasText(headers.schemaName()) ? headers.schemaName() : generateHeadersSchemaName(headers);
String headerSchemaTitle;
headerSchemaTitle =
StringUtils.hasText(headers.schemaName()) ? headers.schemaName() : generateHeaderSchemaName(headers);
String headerDescription =
StringUtils.hasText(headers.description()) ? resolver.resolveStringValue(headers.description()) : null;

Expand Down Expand Up @@ -93,10 +95,6 @@ private static String getDescription(List<AsyncOperation.Headers.Header> value,
.orElse(null);
}

private static String generateHeadersSchemaName(AsyncOperation.Headers headers) {
return "Headers-" + Math.abs(headers.hashCode());
}

public static Map<String, OperationBinding> processOperationBindingFromAnnotation(
Method method, List<OperationBindingProcessor> operationBindingProcessors) {
return operationBindingProcessors.stream()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersNotDocumented;
Expand All @@ -26,7 +27,10 @@
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.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -36,9 +40,11 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class SpringAnnotationMethodLevelChannelsScannerTest {
Expand Down Expand Up @@ -209,6 +215,48 @@ private void methodWithAnnotation(String payload) {}
private void anotherMethodWithAnnotation(SimpleFoo payload) {}
}

@Nested
class HeaderAnnotation {

@Test
void scan_componentHasHeaderAnnotation() {
// given
when(headerClassExtractor.extractHeader(any(), any()))
.thenReturn(SchemaObject.builder()
.type(SchemaType.OBJECT)
.properties(Map.of(
"header_name",
SchemaObject.builder()
.type(SchemaType.STRING)
.examples(List.of("foobar"))
.build()))
.build());

// when
scanner.scan(ClassWithMethodWithHeaderAnnotation.class).toList();

// then
verify(componentsService)
.registerSchema(eq(SchemaObject.builder()
.title("HeadersNotDocumented-934983093")
.type(SchemaType.OBJECT)
.description("There can be headers, but they are not explicitly documented.")
.properties(Map.of(
"header_name",
SchemaObject.builder()
.type(SchemaType.STRING)
.examples(List.of("foobar"))
.build()))
.build()));
}

private static class ClassWithMethodWithHeaderAnnotation {
@TestListener
private void methodWithAnnotationAndHeader(
@Payload String payload, @Header("header_name") String headerValue) {}
}
}

@Data
@NoArgsConstructor
private static class SimpleFoo {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.scanners.common.headers;

import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HeaderSchemaObjectMergerTest {

@Test
void merge() {
SchemaObject initial =
SchemaObject.builder().properties(new HashMap<>()).build();
initial.getProperties().put("key1", "value1");

// when
SchemaObject merged = HeaderSchemaObjectMerger.merge(initial);

// then
assertEquals(
merged,
SchemaObject.builder().properties(Map.of("key1", "value1")).build());
}

@Test
void mergeAndIgnoreNullValues() {
SchemaObject initial =
SchemaObject.builder().properties(new HashMap<>()).build();
initial.getProperties().put("key1", "value1");

// when
SchemaObject merged = HeaderSchemaObjectMerger.merge(
initial,
null,
SchemaObject.builder().build(),
SchemaObject.builder().properties(Collections.emptyMap()).build());

// then
assertEquals(
merged,
SchemaObject.builder().properties(Map.of("key1", "value1")).build());
}

@Test
void mergeWhileNotOverwritingInitialValues() {
SchemaObject initial = SchemaObject.builder()
.title("initial-title")
.description("this-is-initial")
.properties(new HashMap<>())
.build();
initial.getProperties().put("key1", "value1");

SchemaObject schema1 = SchemaObject.builder()
.description("this-is-schema1")
.properties(new HashMap<>())
.build();
schema1.getProperties().put("key1", "value2");
schema1.getProperties().put("key2", "value2");

SchemaObject schema2 = SchemaObject.builder()
.description("this-is-schema2")
.properties(new HashMap<>())
.build();
schema2.getProperties().put("key2", "value3");
schema2.getProperties().put("key3", "value3");

// when
SchemaObject merged = HeaderSchemaObjectMerger.merge(initial, schema1, schema2);

// then
assertEquals(merged.getTitle(), "initial-title-1820791802");
assertEquals(merged.getDescription(), "this-is-initial");
assertEquals(merged.getProperties().size(), 3);
assertEquals(merged.getProperties().get("key1"), "value1");
assertEquals(merged.getProperties().get("key2"), "value2");
assertEquals(merged.getProperties().get("key3"), "value3");
}

@Test
void mergeAndTakeFirstNonNull() {
SchemaObject initial =
SchemaObject.builder().properties(new HashMap<>()).build();

SchemaObject schema1 = SchemaObject.builder()
.description("this-is-schema1")
.properties(new HashMap<>())
.build();
schema1.getProperties().put("key2", "value2");

SchemaObject schema2 = SchemaObject.builder()
.description("this-is-schema2")
.properties(new HashMap<>())
.build();
schema2.getProperties().put("key2", "value3");

// when
SchemaObject merged = HeaderSchemaObjectMerger.merge(initial, schema1, schema2);

// then
assertEquals(merged.getTitle(), "Headers-824725166");
assertEquals(merged.getDescription(), "this-is-schema1");
assertEquals(merged.getProperties().size(), 1);
assertEquals(merged.getProperties().get("key2"), "value2");
}
}
Loading

0 comments on commit 67adc08

Please sign in to comment.