Skip to content
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

feat: handle non-string primitive payloads #726

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 @@ -7,10 +7,12 @@
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

@Getter
@JsonSerialize(using = MessageHeadersSerializer.class)
@EqualsAndHashCode
@ToString
public class MessageHeaders {
private MultiFormatSchema multiFormatSchema;
private SchemaObject schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import jakarta.annotation.Nullable;

import java.util.Map;

public interface ComponentsService {

Map<String, SchemaObject> getSchemas();

@Nullable
SchemaObject resolveSchema(String schemaName);

String registerSchema(SchemaObject headers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.jackson.TypeNameResolver;
import io.swagger.v3.oas.models.media.BooleanSchema;
import io.swagger.v3.oas.models.media.NumberSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
Expand Down Expand Up @@ -127,8 +129,17 @@ public MessageReference registerMessage(MessageObject message) {
}

private String getSchemaName(Class<?> type, Map<String, Schema> schemas) {
if (schemas.isEmpty() && type.equals(String.class)) {
return registerString();
if (schemas.isEmpty()) {
// swagger-parser does not create schemas for primitives
if (type.equals(String.class) || type.equals(Character.class) || type.equals(Byte.class)) {
return registerPrimitive(String.class, new StringSchema());
}
if (Boolean.class.isAssignableFrom(type)) {
return registerPrimitive(Boolean.class, new BooleanSchema());
}
if (Number.class.isAssignableFrom(type)) {
return registerPrimitive(Number.class, new NumberSchema());
}
}

if (schemas.size() == 1) {
Expand Down Expand Up @@ -200,9 +211,8 @@ private void processAsyncApiPayloadAnnotation(Map<String, Schema> schemas, Strin
}
}

private String registerString() {
String schemaName = getNameFromClass(String.class);
StringSchema schema = new StringSchema();
private String registerPrimitive(Class<?> type, Schema schema) {
String schemaName = getNameFromClass(type);
schema.setName(schemaName);

this.schemas.put(schemaName, schema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ private NamedSchemaObject buildSchema(String contentType, Class<?> payloadType)
String componentsSchemaName = this.componentsService.registerSchema(payloadType, contentType);

SchemaObject schema = componentsService.resolveSchema(componentsSchemaName);
schema.setTitle(payloadType.getSimpleName());
if (schema != null) {
schema.setTitle(payloadType.getSimpleName());
}

return new NamedSchemaObject(componentsSchemaName, schema);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.core.asyncapi.components.postprocessors.SchemasPostProcessor;
import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.annotations.media.Schema;
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.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
Expand Down Expand Up @@ -99,7 +101,7 @@ void getDefinitionWithoutFqnClassName() throws IOException {

// when
Class<?> clazz =
OneFieldFooWithFqn.class; // swagger seems to cache results. Therefore, a new class must be used.
OneFieldFooWithoutFqn.class; // swagger seems to cache results. Therefore, a new class must be used.
componentsServiceWithFqn.registerSchema(clazz, "content-type-not-relevant");
String actualDefinitions =
objectMapper.writer(printer).writeValueAsString(componentsServiceWithFqn.getSchemas());
Expand Down Expand Up @@ -133,6 +135,64 @@ void postProcessorIsSkippedWhenSchemaWasRemoved() {
verifyNoInteractions(schemasPostProcessor2);
}

@Nested
class RegisterSchema {

@Test
void Integer() {
// when
componentsService.registerSchema(Integer.class, "content-type-not-relevant");

// then
Map<String, SchemaObject> schemas = componentsService.getSchemas();
assertThat(schemas).hasSize(1).containsKey("java.lang.Number");
assertThat(schemas.get("java.lang.Number").getType()).isEqualTo("number");
}

@Test
void Double() {
// when
componentsService.registerSchema(Double.class, "content-type-not-relevant");

// then
Map<String, SchemaObject> schemas = componentsService.getSchemas();
assertThat(schemas).hasSize(1).containsKey("java.lang.Number");
assertThat(schemas.get("java.lang.Number").getType()).isEqualTo("number");
}

@Test
void String() {
// when
componentsService.registerSchema(String.class, "content-type-not-relevant");

// then
Map<String, SchemaObject> schemas = componentsService.getSchemas();
assertThat(schemas).hasSize(1).containsKey("java.lang.String");
assertThat(schemas.get("java.lang.String").getType()).isEqualTo("string");
}

@Test
void Byte() {
// when
componentsService.registerSchema(Byte.class, "content-type-not-relevant");

// then
Map<String, SchemaObject> schemas = componentsService.getSchemas();
assertThat(schemas).hasSize(1).containsKey("java.lang.String");
assertThat(schemas.get("java.lang.String").getType()).isEqualTo("string");
}

@Test
void Boolean() {
// when
componentsService.registerSchema(Boolean.class, "content-type-not-relevant");

// then
Map<String, SchemaObject> schemas = componentsService.getSchemas();
assertThat(schemas).hasSize(1).containsKey("java.lang.Boolean");
}
}

@Data
@NoArgsConstructor
@Schema(name = "DifferentName")
Expand All @@ -143,7 +203,7 @@ private static class ClassWithSchemaAnnotation {

@Data
@NoArgsConstructor
private static class OneFieldFooWithFqn {
private static class OneFieldFooWithoutFqn {
private String s;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.examples.kafka.consumers;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class IntegerConsumer {
private static final String TOPIC = "integer-topic";

@KafkaListener(topics = TOPIC)
public void receiveIntegerPayload(Integer integerPayload) {
log.info("Received new message in {}: {}", TOPIC, integerPayload);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void testContextWithApplicationProperties() {

@Test
void testAllChannelsAreFound() {
assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(10);
assertThat(asyncApiService.getAsyncAPI().getChannels()).hasSize(11);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@
}
}
},
"integer-topic": {
"messages": {
"java.lang.Number": {
"$ref": "#/components/messages/java.lang.Number"
}
},
"bindings": {
"kafka": {
"bindingVersion": "0.5.0"
}
}
},
"multi-payload-topic": {
"messages": {
"io.github.springwolf.examples.kafka.dtos.AnotherPayloadDto": {
Expand Down Expand Up @@ -428,6 +440,39 @@
"type": "object"
}
},
"SpringKafkaDefaultHeaders-Integer": {
"type": "object",
"properties": {
"__TypeId__": {
"type": "string",
"description": "Spring Type Id Header",
"enum": [
"java.lang.Number"
],
"examples": [
"java.lang.Number"
]
}
},
"examples": [
{
"__TypeId__": "java.lang.Number"
}
],
"x-json-schema": {
"$schema": "https://json-schema.org/draft-04/schema#",
"properties": {
"__TypeId__": {
"description": "Spring Type Id Header",
"enum": [
"java.lang.Number"
],
"type": "string"
}
},
"type": "object"
}
},
"SpringKafkaDefaultHeaders-Message": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1091,6 +1136,16 @@
"type": "string"
}
},
"java.lang.Number": {
"type": "number",
"examples": [
1.1
],
"x-json-schema": {
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "number"
}
},
"java.lang.String": {
"type": "string",
"examples": [
Expand Down Expand Up @@ -1315,6 +1370,24 @@
}
}
},
"java.lang.Number": {
"headers": {
"$ref": "#/components/schemas/SpringKafkaDefaultHeaders-Integer"
},
"payload": {
"schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0",
"schema": {
"$ref": "#/components/schemas/java.lang.Number"
}
},
"name": "java.lang.Number",
"title": "Integer",
"bindings": {
"kafka": {
"bindingVersion": "0.5.0"
}
}
},
"java.lang.String": {
"headers": {
"$ref": "#/components/schemas/SpringKafkaDefaultHeaders-String"
Expand Down Expand Up @@ -1417,6 +1490,22 @@
}
]
},
"integer-topic_receive_receiveIntegerPayload": {
"action": "receive",
"channel": {
"$ref": "#/channels/integer-topic"
},
"bindings": {
"kafka": {
"bindingVersion": "0.5.0"
}
},
"messages": [
{
"$ref": "#/channels/integer-topic/messages/java.lang.Number"
}
]
},
"multi-payload-topic_receive_ExampleClassLevelKafkaListener": {
"action": "receive",
"channel": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ void testFunctionBinding() {
.name(Integer.class.getName())
.title(Integer.class.getSimpleName())
.payload(MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(Integer.class.getSimpleName()))
.schema(SchemaReference.fromSchema(Number.class.getSimpleName()))
.build()))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle())))
Expand Down Expand Up @@ -302,7 +302,7 @@ void testKStreamFunctionBinding() {
.name(Integer.class.getName())
.title(Integer.class.getSimpleName())
.payload(MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(Integer.class.getName()))
.schema(SchemaReference.fromSchema(Number.class.getName()))
.build()))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle())))
Expand Down Expand Up @@ -382,7 +382,7 @@ void testFunctionBindingWithSameTopicName() {
.name(Integer.class.getName())
.title(Integer.class.getSimpleName())
.payload(MessagePayload.of(MultiFormatSchema.builder()
.schema(SchemaReference.fromSchema(Integer.class.getName()))
.schema(SchemaReference.fromSchema(Number.class.getName()))
.build()))
.headers(MessageHeaders.of(
MessageReference.toSchema(AsyncHeadersNotDocumented.NOT_DOCUMENTED.getTitle())))
Expand Down