From 568431030d52abaa595368064957a16434c9f323 Mon Sep 17 00:00:00 2001 From: Timon Back Date: Sat, 3 Aug 2024 16:26:23 +0200 Subject: [PATCH] feat(core): add validation for schema name in AsyncOperation.Headers (#896) motivated by GH-893 --- .../common/utils/AsyncAnnotationUtil.java | 24 ++++--- .../common/utils/AsyncAnnotationUtilTest.java | 63 ++++++++++++++++++- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtil.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtil.java index 7963121b6..25e42f5a1 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtil.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtil.java @@ -35,33 +35,37 @@ public class AsyncAnnotationUtil { private AsyncAnnotationUtil() {} public static SchemaObject getAsyncHeaders(AsyncOperation op, StringValueResolver resolver) { - if (op.headers().values().length == 0) { - if (op.headers().notUsed()) { + AsyncOperation.Headers headers = op.headers(); + if (headers.values().length == 0) { + if (headers.notUsed()) { return AsyncHeadersNotUsed.NOT_USED; } return AsyncHeadersNotDocumented.NOT_DOCUMENTED; } + if (!StringUtils.hasText(headers.schemaName())) { + throw new IllegalArgumentException("The schemaName in @AsyncOperation.Headers must be set for values: " + + Arrays.toString(headers.values())); + } - String headerDescription = StringUtils.hasText(op.headers().description()) - ? resolver.resolveStringValue(op.headers().description()) - : null; + String headerDescription = + StringUtils.hasText(headers.description()) ? resolver.resolveStringValue(headers.description()) : null; SchemaObject headerSchema = new SchemaObject(); headerSchema.setType("object"); - headerSchema.setTitle(op.headers().schemaName()); + headerSchema.setTitle(headers.schemaName()); headerSchema.setDescription(headerDescription); headerSchema.setProperties(new HashMap<>()); - Arrays.stream(op.headers().values()) + Arrays.stream(headers.values()) .collect(groupingBy(AsyncOperation.Headers.Header::name)) - .forEach((headerName, headers) -> { + .forEach((headerName, headersValues) -> { String propertyName = resolver.resolveStringValue(headerName); SchemaObject property = new SchemaObject(); property.setType("string"); property.setTitle(propertyName); - property.setDescription(getDescription(headers, resolver)); - List values = getHeaderValues(headers, resolver); + property.setDescription(getDescription(headersValues, resolver)); + List values = getHeaderValues(headersValues, resolver); property.setExamples(new ArrayList<>(values)); property.setEnumValues(values); headerSchema.getProperties().put(propertyName, property); diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtilTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtilTest.java index e67a5762a..21ff98707 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtilTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/scanners/common/utils/AsyncAnnotationUtilTest.java @@ -13,6 +13,7 @@ import io.github.springwolf.core.asyncapi.scanners.bindings.processor.TestChannelBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.bindings.processor.TestMessageBindingProcessor; import io.github.springwolf.core.asyncapi.scanners.bindings.processor.TestOperationBindingProcessor; +import io.github.springwolf.core.asyncapi.scanners.common.headers.AsyncHeadersNotDocumented; import org.assertj.core.util.Maps; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -24,6 +25,8 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -41,13 +44,13 @@ void getAsyncHeaders(Class classWithOperationBindingProcessor) throws NoSuchM AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation(); StringValueResolver resolver = mock(StringValueResolver.class); - - // when when(resolver.resolveStringValue(any())) .thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved"); - // then + // when SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, resolver); + + // then assertEquals("TestSchema", headers.getTitle()); assertEquals("header-descriptionResolved", headers.getDescription()); assertTrue( @@ -69,6 +72,39 @@ void getAsyncHeaders(Class classWithOperationBindingProcessor) throws NoSuchM assertEquals("descriptionResolved", headerWithoutValueResolved.getDescription()); } + @Test + void getAsyncHeadersWithEmptyHeaders() throws NoSuchMethodException { + // given + Method m = ClassWithHeaders.class.getDeclaredMethod("emptyHeaders", String.class); + AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation(); + + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue(any())) + .thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved"); + + // when + SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, resolver); + + // then + assertThat(headers).isEqualTo(AsyncHeadersNotDocumented.NOT_DOCUMENTED); + } + + @Test + void getAsyncHeadersWithoutSchemaName() throws NoSuchMethodException { + // given + Method m = ClassWithHeaders.class.getDeclaredMethod("withoutSchemaName", String.class); + AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation(); + + StringValueResolver resolver = mock(StringValueResolver.class); + when(resolver.resolveStringValue(any())) + .thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved"); + + // when + then + assertThatIllegalArgumentException() + .isThrownBy(() -> AsyncAnnotationUtil.getAsyncHeaders(operation, resolver)) + .withMessageContaining("The schemaName in @AsyncOperation.Headers must be set for values"); + } + @Test void processOperationBindingFromAnnotation() throws NoSuchMethodException { // given @@ -307,6 +343,27 @@ private void methodWithAnnotation(String payload) {} private void methodWithAsyncMessageAnnotation(String payload) {} } + private static class ClassWithHeaders { + @AsyncListener(operation = @AsyncOperation(channelName = "${test.property.test-channel}")) + @TestOperationBindingProcessor.TestOperationBinding() + private void emptyHeaders(String payload) {} + + @AsyncListener( + operation = + @AsyncOperation( + channelName = "${test.property.test-channel}", + headers = + @AsyncOperation.Headers( + values = { + @AsyncOperation.Headers.Header( + name = "header", + value = "value", + description = "description") + }))) + @TestOperationBindingProcessor.TestOperationBinding() + private void withoutSchemaName(String payload) {} + } + private static class ClassWithMultipleOperationBindingProcessors { @AsyncListener( operation =