Skip to content

Feat/document headers method level protocol listeners #654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 @@ -35,8 +35,17 @@
@interface Headers {
String schemaName() default "";

String description() default "";

Header[] values() default {};

/**
* Indicate that no headers are used in this operation.
* <p>
* All other properties of this annotation are ignored if this is set to true.
*/
boolean notUsed() default false;

@Retention(RetentionPolicy.CLASS)
@Target({})
@Inherited
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public String registerSchema(AsyncHeaders headers) {

MapSchema headerSchema = new MapSchema();
headerSchema.setName(headers.getSchemaName());
headerSchema.setDescription(headers.getDescription());
headerSchema.properties(headers);

this.schemas.put(headers.getSchemaName(), headerSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,32 @@
import java.util.HashMap;

public class AsyncHeaders extends HashMap<String, Schema> {
/**
* Alias to stay backwards-compatible
*/
public static final AsyncHeaders NOT_DOCUMENTED = AsyncHeadersNotDocumented.NOT_DOCUMENTED;
/**
* Explicitly document that no headers are used.
*/
public static final AsyncHeaders NOT_USED = new AsyncHeaders("HeadersNotUsed");

private final String schemaName;
private final String description;

public AsyncHeaders(String schemaName) {
public AsyncHeaders(String schemaName, String description) {
this.schemaName = schemaName;
this.description = description;
}

public AsyncHeaders(String schemaName) {
this(schemaName, null);
}

public String getSchemaName() {
return this.schemaName;
}

public String getDescription() {
return this.description;
}

public void addHeader(AsyncHeaderSchema header) {
this.put(header.getHeaderName(), header);
}

public static AsyncHeaders from(AsyncHeaders source, String newSchemaName) {
AsyncHeaders clone = new AsyncHeaders(newSchemaName);
AsyncHeaders clone = new AsyncHeaders(newSchemaName, source.getDescription());
clone.putAll(source);
return clone;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

public class AsyncHeadersNotDocumented implements AsyncHeadersBuilder {
/**
* Per default, if no headers are explicitly defined, NOT_DOCUMENTED is used.
* Per default, if no headers are explicitly defined, {@link AsyncHeadersNotUsed#NOT_USED} is used.
* There can be headers, but don't have to be.
*/
public static final AsyncHeaders NOT_DOCUMENTED = new AsyncHeaders("HeadersNotDocumented");
public static final AsyncHeaders NOT_DOCUMENTED =
new AsyncHeaders("HeadersNotDocumented", "There can be headers, but they are not explicitly documented.");

@Override
public AsyncHeaders buildHeaders(Class<?> payloadType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.asyncapi.components.headers;

public class AsyncHeadersNotUsed implements AsyncHeadersBuilder {
/**
* Explicitly document that no headers are used.
*/
public static final AsyncHeaders NOT_USED = new AsyncHeaders("HeadersNotUsed", "No headers are present.");

@Override
public AsyncHeaders buildHeaders(Class<?> payloadType) {
return NOT_USED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor;
Expand All @@ -29,9 +30,10 @@ public class SpringAnnotationMethodLevelChannelsScanner<MethodAnnotation extends
public SpringAnnotationMethodLevelChannelsScanner(
Class<MethodAnnotation> methodAnnotationClass,
BindingFactory<MethodAnnotation> bindingFactory,
AsyncHeadersBuilder asyncHeadersBuilder,
PayloadClassExtractor payloadClassExtractor,
ComponentsService componentsService) {
super(bindingFactory, componentsService);
super(bindingFactory, asyncHeadersBuilder, componentsService);
this.methodAnnotationClass = methodAnnotationClass;
this.payloadClassExtractor = payloadClassExtractor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ protected MessageObject buildMessage(ClassAnnotation classAnnotation, Class<?> p
Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(classAnnotation);
String modelName = componentsService.registerSchema(payloadType);
String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType));

MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(modelName))
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import io.github.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaReference;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaders;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -22,12 +22,13 @@
public abstract class MethodLevelAnnotationScanner<MethodAnnotation extends Annotation> {

protected final BindingFactory<MethodAnnotation> bindingFactory;
protected final AsyncHeadersBuilder asyncHeadersBuilder;
protected final ComponentsService componentsService;

protected MessageObject buildMessage(MethodAnnotation annotation, Class<?> payloadType) {
Map<String, MessageBinding> messageBinding = bindingFactory.buildMessageBinding(annotation);
String modelName = componentsService.registerSchema(payloadType);
String headerModelName = componentsService.registerSchema(AsyncHeaders.NOT_DOCUMENTED);
String headerModelName = componentsService.registerSchema(asyncHeadersBuilder.buildHeaders(payloadType));
MessagePayload payload = MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(modelName))
.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import io.github.springwolf.core.asyncapi.annotations.AsyncOperation;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaderSchema;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaders;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotUsed;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ChannelBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.channels.ProcessedChannelBinding;
import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor;
Expand All @@ -32,10 +34,16 @@ private AsyncAnnotationUtil() {}

public static AsyncHeaders getAsyncHeaders(AsyncOperation op, StringValueResolver resolver) {
if (op.headers().values().length == 0) {
return AsyncHeaders.NOT_DOCUMENTED;
if (op.headers().notUsed()) {
return AsyncHeadersNotUsed.NOT_USED;
}
return AsyncHeadersNotDocumented.NOT_DOCUMENTED;
}

AsyncHeaders asyncHeaders = new AsyncHeaders(op.headers().schemaName());
String headerDescription = StringUtils.hasText(op.headers().description())
? resolver.resolveStringValue(op.headers().description())
: null;
AsyncHeaders asyncHeaders = new AsyncHeaders(op.headers().schemaName(), headerDescription);
Arrays.stream(op.headers().values())
.collect(groupingBy(AsyncOperation.Headers.Header::name))
.forEach((headerName, headers) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
import io.github.springwolf.asyncapi.v3.model.operation.OperationAction;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersBuilder;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.MethodLevelAnnotationScanner;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor;
Expand All @@ -32,9 +33,10 @@ public class SpringAnnotationMethodLevelOperationsScanner<MethodAnnotation exten
public SpringAnnotationMethodLevelOperationsScanner(
Class<MethodAnnotation> methodAnnotationClass,
BindingFactory<MethodAnnotation> bindingFactory,
AsyncHeadersBuilder asyncHeadersBuilder,
PayloadClassExtractor payloadClassExtractor,
ComponentsService componentsService) {
super(bindingFactory, componentsService);
super(bindingFactory, asyncHeadersBuilder, componentsService);
this.methodAnnotationClass = methodAnnotationClass;
this.payloadClassExtractor = payloadClassExtractor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.asyncapi.components.DefaultComponentsService;
import io.github.springwolf.core.asyncapi.components.SwaggerSchemaUtil;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaders;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented;
import io.github.springwolf.core.asyncapi.scanners.bindings.messages.MessageBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.operations.OperationBindingProcessor;
import io.github.springwolf.core.asyncapi.scanners.bindings.processor.TestOperationBindingProcessor;
Expand Down Expand Up @@ -155,7 +155,8 @@ void scan_componentChannelHasListenerMethod() {
.title(SimpleFoo.class.getSimpleName())
.description("SimpleFoo Message Description")
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(EMPTY_MAP)
.build();

Expand Down Expand Up @@ -228,7 +229,8 @@ void scan_componentHasMultipleListenerAnnotations() {
.name(SimpleFoo.class.getName())
.title(SimpleFoo.class.getSimpleName())
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(EMPTY_MAP)
.description("SimpleFoo Message Description")
.build();
Expand Down Expand Up @@ -268,7 +270,8 @@ void scan_componentHasAsyncMethodAnnotation() {
.title("Message Title")
.description("Message description")
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(EMPTY_MAP)
.build();

Expand Down Expand Up @@ -374,7 +377,8 @@ void scan_componentHasOnlyDeclaredMethods(Class<?> clazz) {
.title(String.class.getSimpleName())
.description(null)
.payload(messagePayload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(EMPTY_MAP)
.build();

Expand Down Expand Up @@ -438,7 +442,8 @@ void scan_componentHasListenerMethodWithMetaAnnotation() {
.title(String.class.getSimpleName())
.description(null)
.payload(messagePayload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(EMPTY_MAP)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void setUp() {
doAnswer(invocation -> invocation.<Class<?>>getArgument(0).getSimpleName())
.when(componentsService)
.registerSchema(any(Class.class));
doAnswer(invocation -> AsyncHeaders.NOT_DOCUMENTED.getSchemaName())
doAnswer(invocation -> AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())
.when(componentsService)
.registerSchema(any(AsyncHeaders.class));
}
Expand All @@ -92,7 +92,8 @@ void scan_componentHasTestListenerMethods() {
.name(String.class.getName())
.title(String.class.getSimpleName())
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(defaultMessageBinding)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import io.github.springwolf.core.asyncapi.components.examples.SchemaWalkerProvider;
import io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker;
import io.github.springwolf.core.asyncapi.components.examples.walkers.json.ExampleJsonValueGenerator;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeaders;
import io.github.springwolf.core.asyncapi.components.headers.AsyncHeadersNotDocumented;
import io.github.springwolf.core.asyncapi.scanners.bindings.BindingFactory;
import io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadClassExtractor;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
Expand Down Expand Up @@ -71,7 +71,11 @@ class SpringAnnotationMethodLevelChannelsScannerIntegrationTest {
@BeforeEach
void setUp() {
scanner = new SpringAnnotationMethodLevelChannelsScanner<>(
TestChannelListener.class, this.bindingFactory, payloadClassExtractor, componentsService);
TestChannelListener.class,
this.bindingFactory,
new AsyncHeadersNotDocumented(),
payloadClassExtractor,
componentsService);
}

@Nested
Expand Down Expand Up @@ -109,7 +113,8 @@ void scan_componentHasListenerMethod() {
.name(SimpleFoo.class.getName())
.title(SimpleFoo.class.getSimpleName())
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(TestBindingFactory.defaultMessageBinding)
.build();

Expand Down Expand Up @@ -152,15 +157,17 @@ void scan_componentHasTestListenerMethods_multiplePayloads() {
.name(SimpleFoo.class.getName())
.title(SimpleFoo.class.getSimpleName())
.payload(simpleFooPayload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(TestBindingFactory.defaultMessageBinding)
.build();
MessageObject messageString = MessageObject.builder()
.messageId(String.class.getName())
.name(String.class.getName())
.title(String.class.getSimpleName())
.payload(stringPayload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(TestBindingFactory.defaultMessageBinding)
.build();

Expand Down Expand Up @@ -209,7 +216,8 @@ void scan_componentHasListenerMetaMethod() {
.name(SimpleFoo.class.getName())
.title(SimpleFoo.class.getSimpleName())
.payload(payload)
.headers(MessageHeaders.of(MessageReference.toSchema(AsyncHeaders.NOT_DOCUMENTED.getSchemaName())))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())))
.bindings(defaultMessageBinding)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ class SpringAnnotationMethodLevelChannelsScannerTest {
private final BindingFactory<TestListener> bindingFactory = mock(BindingFactory.class);
private final ComponentsService componentsService = mock(ComponentsService.class);
SpringAnnotationMethodLevelChannelsScanner<TestListener> scanner = new SpringAnnotationMethodLevelChannelsScanner<>(
TestListener.class, bindingFactory, payloadClassExtractor, componentsService);
TestListener.class,
bindingFactory,
new AsyncHeadersNotDocumented(),
payloadClassExtractor,
componentsService);

private static final String CHANNEL = "test-channel";
private static final Map<String, OperationBinding> defaultOperationBinding =
Expand All @@ -66,7 +70,7 @@ void setUp() throws NoSuchMethodException {
doAnswer(invocation -> invocation.<Class<?>>getArgument(0).getSimpleName())
.when(componentsService)
.registerSchema(any(Class.class));
doAnswer(invocation -> AsyncHeaders.NOT_DOCUMENTED.getSchemaName())
doAnswer(invocation -> AsyncHeadersNotDocumented.NOT_DOCUMENTED.getSchemaName())
.when(componentsService)
.registerSchema(any(AsyncHeaders.class));

Expand Down
Loading