Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@
String description() default "";

String value() default "";

/**
* The format of the header value according to AsyncAPI specification.
* <p>
* Common formats include:
* <ul>
* <li>"int32" - 32-bit signed integer</li>
* <li>"int64" - 64-bit signed integer</li>
* <li>"date" - RFC 3339 date</li>
* <li>"date-time" - RFC 3339 date-time</li>
* </ul>
*
* @see <a href="https://www.asyncapi.com/docs/reference/specification/v3.0.0#dataTypeFormat">AsyncAPI Data Type Format</a>
* @return the format string, empty by default
*/
String format() default "";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@ public static SchemaObject getAsyncHeaders(AsyncOperation op, StringValueResolve

SchemaObject property = new SchemaObject();
property.setType(SchemaType.STRING);

property.setTitle(propertyName);
property.setDescription(getDescription(headersValues, stringValueResolver));
property.setFormat(getFormat(headersValues, stringValueResolver));
List<String> values = getHeaderValues(headersValues, stringValueResolver);
if (!values.isEmpty()) {
property.setExamples(new ArrayList<>(values));
Expand All @@ -84,7 +86,7 @@ private static List<String> getHeaderValues(
return value.stream()
.map(AsyncOperation.Headers.Header::value)
.filter(StringUtils::hasText)
.map(stringValueResolver::resolveStringValue)
.flatMap(text -> Optional.ofNullable(stringValueResolver.resolveStringValue(text)).stream())
.sorted()
.toList();
}
Expand All @@ -93,8 +95,19 @@ private static String getDescription(
List<AsyncOperation.Headers.Header> value, StringValueResolver stringValueResolver) {
return value.stream()
.map(AsyncOperation.Headers.Header::description)
.map(stringValueResolver::resolveStringValue)
.filter(StringUtils::hasText)
.flatMap(text -> Optional.ofNullable(stringValueResolver.resolveStringValue(text)).stream())
.sorted()
.findFirst()
.orElse(null);
}

private static String getFormat(
List<AsyncOperation.Headers.Header> value, StringValueResolver stringValueResolver) {
return value.stream()
.map(AsyncOperation.Headers.Header::format)
.filter(StringUtils::hasText)
.flatMap(text -> Optional.ofNullable(stringValueResolver.resolveStringValue(text)).stream())
.sorted()
.findFirst()
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ private Schema mapSchemaObjectToSwagger(SchemaObject asyncApiSchema) {
.orElse(null));
swaggerSchema.setTypes(asyncApiSchema.getType());
}
// swaggerSchema.setFormat(asyncApiSchema.getFormat());
swaggerSchema.setFormat(asyncApiSchema.getFormat());
swaggerSchema.setDescription(asyncApiSchema.getDescription());
swaggerSchema.setExamples(asyncApiSchema.getExamples());
swaggerSchema.setEnum(asyncApiSchema.getEnumValues());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,18 @@
class AsyncAnnotationUtilTest {
StringValueResolver stringValueResolver = mock(StringValueResolver.class);

{
when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");
}

@ParameterizedTest
@ValueSource(classes = {ClassWithOperationBindingProcessor.class, ClassWithAbstractOperationBindingProcessor.class})
void getAsyncHeaders(Class<?> classWithOperationBindingProcessor) throws Exception {
// given
Method m = classWithOperationBindingProcessor.getDeclaredMethod("methodWithAnnotation", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");

// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);

Expand All @@ -58,6 +60,7 @@ void getAsyncHeaders(Class<?> classWithOperationBindingProcessor) throws Excepti
assertThat(headerResolved.getType()).containsExactly("string");
assertThat(headerResolved.getExamples().get(0)).isEqualTo("valueResolved");
assertThat(headerResolved.getDescription()).isEqualTo("descriptionResolved");
assertThat(headerResolved.getFormat()).isEqualTo("int32Resolved");

assertThat(headers.getProperties().containsKey("headerWithoutValueResolved"))
.as(headers.getProperties() + " does not contain key 'headerWithoutValueResolved'")
Expand All @@ -68,6 +71,7 @@ void getAsyncHeaders(Class<?> classWithOperationBindingProcessor) throws Excepti
assertThat(headerWithoutValueResolved.getExamples()).isNull();
assertThat(headerWithoutValueResolved.getEnumValues()).isNull();
assertThat(headerWithoutValueResolved.getDescription()).isEqualTo("descriptionResolved");
assertThat(headerWithoutValueResolved.getFormat()).isNull();
}

@Test
Expand All @@ -76,10 +80,6 @@ void getAsyncHeadersWithEmptyHeaders() throws Exception {
Method m = ClassWithHeaders.class.getDeclaredMethod("emptyHeaders", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

StringValueResolver stringValueResolver = mock(StringValueResolver.class);
when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");

// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);

Expand All @@ -93,24 +93,21 @@ void getAsyncHeadersWithoutSchemaName() throws Exception {
Method m = ClassWithHeaders.class.getDeclaredMethod("withoutSchemaName", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

StringValueResolver stringValueResolver = mock(StringValueResolver.class);
when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");

// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);

// then
assertThat(headers)
.isEqualTo(SchemaObject.builder()
.type(SchemaType.OBJECT)
.title("Headers-501004016")
.title("Headers-1585401221")
.properties(Map.of(
"headerResolved",
SchemaObject.builder()
.type(SchemaType.STRING)
.title("headerResolved")
.description("descriptionResolved")
.format(null)
.enumValues(List.of("valueResolved"))
.examples(List.of("valueResolved"))
.build()))
Expand All @@ -123,9 +120,32 @@ void getAsyncHeadersWithoutValue() throws Exception {
Method m = ClassWithHeaders.class.getDeclaredMethod("withoutValue", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

StringValueResolver stringValueResolver = mock(StringValueResolver.class);
when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");
// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);

// then
assertThat(headers)
.isEqualTo(SchemaObject.builder()
.type(SchemaType.OBJECT)
.title("Headers-1612438838")
.properties(Map.of(
"headerResolved",
SchemaObject.builder()
.type(SchemaType.STRING)
.title("headerResolved")
.description("descriptionResolved")
.format(null)
.enumValues(null)
.examples(null)
.build()))
.build());
}

@Test
void getAsyncHeadersWithFormat() throws Exception {
// given
Method m = ClassWithHeaders.class.getDeclaredMethod("withFormat", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);
Expand All @@ -134,11 +154,12 @@ void getAsyncHeadersWithoutValue() throws Exception {
assertThat(headers)
.isEqualTo(SchemaObject.builder()
.type(SchemaType.OBJECT)
.title("Headers-472917891")
.title("Headers-1701213112")
.properties(Map.of(
"headerResolved",
SchemaObject.builder()
.type(SchemaType.STRING)
.format("int32Resolved")
.title("headerResolved")
.description("descriptionResolved")
.enumValues(null)
Expand All @@ -147,6 +168,20 @@ void getAsyncHeadersWithoutValue() throws Exception {
.build());
}

@Test
void getAsyncHeadersWithEmptyFormat() throws Exception {
// given
Method m = ClassWithHeaders.class.getDeclaredMethod("withoutFormat", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

// when
SchemaObject headers = AsyncAnnotationUtil.getAsyncHeaders(operation, stringValueResolver);

// then
SchemaObject headerProperty = (SchemaObject) headers.getProperties().get("headerResolved");
assertThat(headerProperty.getFormat()).isNull();
}

@Test
void generatedHeaderSchemaNameShouldBeUnique() throws Exception {
// given
Expand All @@ -156,10 +191,6 @@ void generatedHeaderSchemaNameShouldBeUnique() throws Exception {
Method m2 = ClassWithHeaders.class.getDeclaredMethod("differentHeadersWithoutSchemaName", String.class);
AsyncOperation operation2 = m2.getAnnotation(AsyncListener.class).operation();

StringValueResolver stringValueResolver = mock(StringValueResolver.class);
when(stringValueResolver.resolveStringValue(any()))
.thenAnswer(invocation -> invocation.getArgument(0).toString() + "Resolved");

// when
SchemaObject headers1 = AsyncAnnotationUtil.getAsyncHeaders(operation1, stringValueResolver);
SchemaObject headers2 = AsyncAnnotationUtil.getAsyncHeaders(operation2, stringValueResolver);
Expand Down Expand Up @@ -286,8 +317,6 @@ void getServers() throws Exception {
Method m = ClassWithOperationBindingProcessor.class.getDeclaredMethod("methodWithAnnotation", String.class);
AsyncOperation operation = m.getAnnotation(AsyncListener.class).operation();

StringValueResolver stringValueResolver = mock(StringValueResolver.class);

// when
when(stringValueResolver.resolveStringValue("${test.property.server1}")).thenReturn("server1");

Expand Down Expand Up @@ -351,7 +380,8 @@ private static class ClassWithOperationBindingProcessor {
@AsyncOperation.Headers.Header(
name = "header",
value = "value",
description = "description"),
description = "description",
format = "int32"),
@AsyncOperation.Headers.Header(
name = "headerWithoutValue",
description = "description")
Expand Down Expand Up @@ -398,7 +428,8 @@ private static class ClassWithAbstractOperationBindingProcessor {
@AsyncOperation.Headers.Header(
name = "header",
value = "value",
description = "description"),
description = "description",
format = "int32"),
@AsyncOperation.Headers.Header(
name = "headerWithoutValue",
description = "description")
Expand Down Expand Up @@ -465,6 +496,35 @@ private void withoutSchemaName(String payload) {}
@TestOperationBindingProcessor.TestOperationBinding()
private void withoutValue(String payload) {}

@AsyncListener(
operation =
@AsyncOperation(
channelName = "${test.property.test-channel}",
headers =
@AsyncOperation.Headers(
values = {
@AsyncOperation.Headers.Header(
name = "header",
description = "description",
format = "int32")
})))
@TestOperationBindingProcessor.TestOperationBinding()
private void withFormat(String payload) {}

@AsyncListener(
operation =
@AsyncOperation(
channelName = "${test.property.test-channel}",
headers =
@AsyncOperation.Headers(
values = {
@AsyncOperation.Headers.Header(
name = "header",
description = "description")
})))
@TestOperationBindingProcessor.TestOperationBinding()
private void withoutFormat(String payload) {}

@AsyncListener(
operation =
@AsyncOperation(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.kafka.producers;

import io.github.springwolf.bindings.kafka.annotations.KafkaAsyncOperationBinding;
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.annotations.AsyncPublisher;
import io.github.springwolf.examples.kafka.configuration.KafkaConfiguration;
import io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

import static org.springframework.kafka.support.mapping.AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME;

@Component
public class AnotherProducer {

@Autowired
private KafkaTemplate<String, AnotherPayloadDto> kafkaTemplate;

@AsyncPublisher(
operation =
@AsyncOperation(
channelName = "another-topic",
headers =
@AsyncOperation.Headers(
schemaName = "SpringKafkaDefaultHeaders-AnotherTopic",
values = {
@AsyncOperation.Headers.Header(
name = DEFAULT_CLASSID_FIELD_NAME,
description = "Type ID"),
@AsyncOperation.Headers.Header(
name = "my_uuid_field",
description = "Event identifier",
format = "uuid")
})))
@KafkaAsyncOperationBinding
public void sendMessage(AnotherPayloadDto msg) {
kafkaTemplate.send(KafkaConfiguration.PRODUCER_TOPIC, msg);
}
Expand Down
Loading
Loading