Skip to content

Commit

Permalink
feat(core): allow publishing of all data types (#759)
Browse files Browse the repository at this point in the history
  • Loading branch information
sam0r040 authored May 17, 2024
1 parent d264dd1 commit ee1de1c
Show file tree
Hide file tree
Showing 3 changed files with 343 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.controller.dtos.MessageDto;
import jakarta.annotation.Nullable;
Expand All @@ -11,6 +13,7 @@
import org.apache.commons.lang3.StringUtils;

import java.text.MessageFormat;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

Expand All @@ -32,17 +35,20 @@ public Result createPayloadObject(MessageDto message) {
return new Result(null, Optional.empty());
}

Set<String> knownSchemaNames = componentsService.getSchemas().keySet();
for (String schemaPayloadType : knownSchemaNames) {
Map<String, SchemaObject> knownSchemas = componentsService.getSchemas();
Set<String> knownSchemaNames = knownSchemas.keySet();
for (Map.Entry<String, SchemaObject> schemaEntry : knownSchemas.entrySet()) {
String schemaName = schemaEntry.getKey();
SchemaObject schema = schemaEntry.getValue();

// security: match against user input, but always use our controlled data from the DefaultSchemaService
if (schemaPayloadType != null && schemaPayloadType.equals(messagePayloadType)) {
if (schemaName != null && schemaName.equals(messagePayloadType)) {
try {
Class<?> payloadClass = Class.forName(schemaPayloadType);
Object payload = objectMapper.readValue(message.getPayload(), payloadClass);
Object payload = resolveActualPayload(message, schema, schemaName);
return new Result(payload, Optional.empty());
} catch (ClassNotFoundException | JsonProcessingException ex) {
} catch (ClassNotFoundException | JsonProcessingException | IllegalArgumentException ex) {
String errorMessage = MessageFormat.format(
"Unable to create payload {0} from data: {1}", schemaPayloadType, message.getPayload());
"Unable to create payload {0} from data: {1}", schemaName, message.getPayload());
log.info(errorMessage, ex);
return new Result(null, Optional.of(errorMessage));
}
Expand All @@ -57,5 +63,28 @@ public Result createPayloadObject(MessageDto message) {
return new Result(null, Optional.of(errorMessage));
}

private Object resolveActualPayload(MessageDto message, SchemaObject schema, String schemaName)
throws ClassNotFoundException, JsonProcessingException, IllegalArgumentException {
switch (schema.getType()) {
case SchemaType.BOOLEAN -> {
return objectMapper.readValue(message.getPayload(), Boolean.class);
}
case SchemaType.INTEGER -> {
return objectMapper.readValue(message.getPayload(), Long.class);
}
case SchemaType.NUMBER -> {
return objectMapper.readValue(message.getPayload(), Double.class);
}
case SchemaType.OBJECT -> {
Class<?> payloadClass = Class.forName(schemaName);
return objectMapper.readValue(message.getPayload(), payloadClass);
}
case SchemaType.STRING -> {
return objectMapper.readValue(message.getPayload(), String.class);
}
default -> throw new IllegalArgumentException("Unsupported schema type: " + schema.getType());
}
}

public record Result(@Nullable Object payload, Optional<String> errorMessage) {}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.controller.dtos;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
Expand All @@ -10,6 +11,7 @@
@Data
@Builder
@Jacksonized
@AllArgsConstructor
public class MessageDto {
public static final String EMPTY = "";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.core.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaObject;
import io.github.springwolf.asyncapi.v3.model.schema.SchemaType;
import io.github.springwolf.core.asyncapi.components.ComponentsService;
import io.github.springwolf.core.controller.dtos.MessageDto;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class PublishingPayloadCreatorTest {

@Mock
private ComponentsService componentsService;

@Mock
private ObjectMapper objectMapper;

@InjectMocks
private PublishingPayloadCreator publishingPayloadCreator;

@Test
void shouldResolveEmptyPayload() {
// given
String payloadType = ObjectClass.class.getName();
String payload = "";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(null, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveBooleanPayload() throws JsonProcessingException {
// given
String payloadType = Boolean.class.getName();
String payload = "true";
Boolean payloadTyped = true;
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.BOOLEAN).build()));
when(objectMapper.readValue(payload, Boolean.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveIntegerPayload() throws JsonProcessingException {
// given
String payloadType = Integer.class.getName();
String payload = "12345678";
Long payloadTyped = 12345678L;
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.INTEGER).build()));
when(objectMapper.readValue(payload, Long.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveLongPayload() throws JsonProcessingException {
// given
String payloadType = Long.class.getName();
String payload = Long.valueOf(Long.MAX_VALUE).toString();
Long payloadTyped = Long.MAX_VALUE;
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.INTEGER).build()));
when(objectMapper.readValue(payload, Long.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveFloatPayload() throws JsonProcessingException {
// given
String payloadType = Float.class.getName();
String payload = "12345678.123";
Double payloadTyped = 12345678.123;
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.NUMBER).build()));
when(objectMapper.readValue(payload, Double.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveDoublePayload() throws JsonProcessingException {
// given
String payloadType = Double.class.getName();
String payload = Double.valueOf(Double.MAX_VALUE).toString();
Double payloadTyped = Double.MAX_VALUE;
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.NUMBER).build()));
when(objectMapper.readValue(payload, Double.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveObjectPayload() throws JsonProcessingException {
// given
String payloadType = ObjectClass.class.getName();
String payload = "{\"value\":\"test\"}";
ObjectClass payloadTyped = new ObjectClass("test");
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.OBJECT).build()));
when(objectMapper.readValue(payload, ObjectClass.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldResolveStringPayload() throws JsonProcessingException {
// given
String payloadType = String.class.getName();
String payload = "\"test\"";
String payloadTyped = "test";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.STRING).build()));
when(objectMapper.readValue(payload, String.class)).thenReturn(payloadTyped);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(payloadTyped, result.payload());
assertEquals(Optional.empty(), result.errorMessage());
}

@Test
void shouldReturnEmptyPayloadForUnknownClass() {
// given
String payloadType = "MyUnknownClass";
String payload = "payload-data";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.OBJECT).build()));

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(null, result.payload());
assertEquals(
Optional.of("Unable to create payload MyUnknownClass from data: " + payload), result.errorMessage());
}

@Test
void shouldReturnEmptyPayloadForInvalidJson() throws JsonProcessingException {
// given
String payloadType = ObjectClass.class.getName();
String payload = "---invalid-json---";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.OBJECT).build()));
when(objectMapper.readValue(payload, ObjectClass.class))
.thenThrow(new JsonProcessingException("invalid json") {});

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(null, result.payload());
assertEquals(
Optional.of(
"Unable to create payload io.github.springwolf.core.controller.PublishingPayloadCreatorTest$ObjectClass from data: "
+ payload),
result.errorMessage());
}

@Test
void shouldReturnEmptyPayloadForUnsupportedSchemaType() {
// given
String payloadType = ObjectClass.class.getName();
String payload = "{}";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

when(componentsService.getSchemas())
.thenReturn(Map.of(
payloadType,
SchemaObject.builder().type(SchemaType.ARRAY).build()));

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(null, result.payload());
assertEquals(
Optional.of(
"Unable to create payload io.github.springwolf.core.controller.PublishingPayloadCreatorTest$ObjectClass from data: "
+ payload),
result.errorMessage());
}

@Test
void shouldReturnEmptyPayloadForUnmatchedPayloadType() {
// given
String payloadType = String.class.getName();
String payload = "{\"value\":\"test\"}";
MessageDto message = new MessageDto(Map.of(), Map.of(), payloadType, payload);

// when
PublishingPayloadCreator.Result result = publishingPayloadCreator.createPayloadObject(message);

// then
assertNotNull(result);
assertEquals(null, result.payload());
assertEquals(
Optional.of("Specified payloadType java.lang.String is not a registered springwolf schema."),
result.errorMessage());
}

record ObjectClass(String value) {}
}

0 comments on commit ee1de1c

Please sign in to comment.