feat: JSON format to assume JSON content-type where possible (#604)

Signed-off-by: Matej Vašek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek 2024-02-08 09:49:19 +01:00 committed by GitHub
parent 7b9d020acc
commit 55fddb35fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 98 additions and 8 deletions

View File

@ -42,18 +42,21 @@ import java.io.IOException;
class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
private final boolean disableDataContentTypeDefaulting;
protected CloudEventDeserializer() {
this(false, false);
this(false, false, false);
}
protected CloudEventDeserializer(
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
boolean forceIgnoreInvalidExtensionNameDeserialization,
boolean disableDataContentTypeDefaulting
) {
super(CloudEvent.class);
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
}
private static class JsonMessage implements CloudEventReader {
@ -62,17 +65,20 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
private final ObjectNode node;
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
private final boolean disableDataContentTypeDefaulting;
public JsonMessage(
JsonParser p,
ObjectNode node,
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
boolean forceIgnoreInvalidExtensionNameDeserialization,
boolean disableDataContentTypeDefaulting
) {
this.p = p;
this.node = node;
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
}
@Override
@ -92,6 +98,9 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
// Parse datacontenttype if any
String contentType = getOptionalStringNode(this.node, this.p, "datacontenttype");
if (!this.disableDataContentTypeDefaulting && contentType == null && this.node.has("data")) {
contentType = "application/json";
}
if (contentType != null) {
writer.withContextAttribute("datacontenttype", contentType);
}
@ -257,7 +266,7 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
ObjectNode node = ctxt.readValue(p, ObjectNode.class);
try {
return new JsonMessage(p, node, this.forceExtensionNameLowerCaseDeserialization, this.forceIgnoreInvalidExtensionNameDeserialization)
return new JsonMessage(p, node, this.forceExtensionNameLowerCaseDeserialization, this.forceIgnoreInvalidExtensionNameDeserialization, this.disableDataContentTypeDefaulting)
.read(CloudEventBuilder::fromSpecVersion);
} catch (RuntimeException e) {
// Yeah this is bad but it's needed to support checked exceptions...

View File

@ -213,7 +213,7 @@ public final class JsonFormat implements EventFormat {
ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer(
options.isForceDataBase64Serialization(), options.isForceStringSerialization()));
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer(
options.isForceExtensionNameLowerCaseDeserialization(), options.isForceIgnoreInvalidExtensionNameDeserialization()));
options.isForceExtensionNameLowerCaseDeserialization(), options.isForceIgnoreInvalidExtensionNameDeserialization(), options.isDataContentTypeDefaultingDisabled()));
return ceModule;
}

View File

@ -21,24 +21,27 @@ public final class JsonFormatOptions {
private final boolean forceStringSerialization;
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
private final boolean disableDataContentTypeDefaulting;
/**
* Create a new instance of this class options the serialization / deserialization.
*/
public JsonFormatOptions() {
this(false, false, false, false);
this(false, false, false, false, false);
}
JsonFormatOptions(
boolean forceDataBase64Serialization,
boolean forceStringSerialization,
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
boolean forceIgnoreInvalidExtensionNameDeserialization,
boolean disableDataContentTypeDefaulting
) {
this.forceDataBase64Serialization = forceDataBase64Serialization;
this.forceStringSerialization = forceStringSerialization;
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
}
public static JsonFormatOptionsBuilder builder() {
@ -61,11 +64,14 @@ public final class JsonFormatOptions {
return this.forceIgnoreInvalidExtensionNameDeserialization;
}
public boolean isDataContentTypeDefaultingDisabled() { return this.disableDataContentTypeDefaulting; }
public static class JsonFormatOptionsBuilder {
private boolean forceDataBase64Serialization = false;
private boolean forceStringSerialization = false;
private boolean forceExtensionNameLowerCaseDeserialization = false;
private boolean forceIgnoreInvalidExtensionNameDeserialization = false;
private boolean disableDataContentTypeDefaulting = false;
public JsonFormatOptionsBuilder forceDataBase64Serialization(boolean forceDataBase64Serialization) {
this.forceDataBase64Serialization = forceDataBase64Serialization;
@ -87,12 +93,18 @@ public final class JsonFormatOptions {
return this;
}
public JsonFormatOptionsBuilder disableDataContentTypeDefaulting(boolean disableDataContentTypeDefaulting) {
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
return this;
}
public JsonFormatOptions build() {
return new JsonFormatOptions(
this.forceDataBase64Serialization,
this.forceStringSerialization,
this.forceExtensionNameLowerCaseDeserialization,
this.forceIgnoreInvalidExtensionNameDeserialization
this.forceIgnoreInvalidExtensionNameDeserialization,
this.disableDataContentTypeDefaulting
);
}
}

View File

@ -0,0 +1,69 @@
package io.cloudevents.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.cloudevents.CloudEvent;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import static io.cloudevents.jackson.JsonFormat.getCloudEventJacksonModule;
import static org.assertj.core.api.Assertions.assertThat;
public class CloudEventDeserializerTest {
private static final String nonBinaryPayload = "{\n" +
" \"specversion\" : \"1.0\",\n" +
" \"type\" : \"com.example.someevent\",\n" +
" \"source\" : \"/mycontext\",\n" +
" \"subject\": null,\n" +
" \"id\" : \"D234-1234-1234\",\n" +
" \"time\" : \"2018-04-05T17:31:00Z\",\n" +
" \"comexampleextension1\" : \"value\",\n" +
" \"comexampleothervalue\" : 5,\n" +
" \"data\" : \"I'm just a string\"\n" +
"}";
private static final String binaryPayload = "{\n" +
" \"specversion\" : \"1.0\",\n" +
" \"type\" : \"com.example.someevent\",\n" +
" \"source\" : \"/mycontext\",\n" +
" \"id\" : \"D234-1234-1234\",\n" +
" \"data_base64\" : \"eyAieHl6IjogMTIzIH0=\"\n" +
"}";
@Test
void impliedDataContentTypeNonBinaryData() throws IOException {
ObjectMapper mapper = getObjectMapper(false);
StringReader reader = new StringReader(nonBinaryPayload);
CloudEvent ce = mapper.readValue(reader, CloudEvent.class);
assertThat(ce.getDataContentType()).isEqualTo("application/json");
mapper = getObjectMapper(true);
reader = new StringReader(nonBinaryPayload);
ce = mapper.readValue(reader, CloudEvent.class);
assertThat(ce.getDataContentType()).isNull();
}
@Test
void impliedDataContentTypeBinaryData() throws IOException {
final ObjectMapper mapper = getObjectMapper(false);
StringReader reader = new StringReader(binaryPayload);
CloudEvent ce = mapper.readValue(reader, CloudEvent.class);
assertThat(ce.getDataContentType()).isNull();
}
private static ObjectMapper getObjectMapper(boolean disableDataContentTypeDefaulting) {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = getCloudEventJacksonModule(
JsonFormatOptions
.builder()
.disableDataContentTypeDefaulting(disableDataContentTypeDefaulting)
.build()
);
mapper.registerModule(module);
return mapper;
}
}