Skip to content

Commit

Permalink
Feat/document headers method level protocol listeners (springwolf#654)
Browse files Browse the repository at this point in the history
* feat: document headers for method level listeners

* feat(core): add header description

* feat(ui): show schema type first in collapsible header

* feat(core): add simple option to define no headers are used

* refactor(core): resolve static initialization of AsyncHeaders.NOT_DOCUMENTED

Moved both constants to an own class. The constants have been relicts when users could use the constants directly in the AsyncApiDocket.
This is not possible with 1.0.0, therefore this is no breaking change.

---------

Co-authored-by: sam0r040 <93372330+sam0r040@users.noreply.github.com>
  • Loading branch information
timonback and sam0r040 authored Mar 22, 2024
1 parent 3876881 commit bbedd4f
Show file tree
Hide file tree
Showing 33 changed files with 380 additions and 72 deletions.
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

0 comments on commit bbedd4f

Please sign in to comment.