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: JSON format to assume JSON content-type where possible #604

Merged
merged 1 commit into from
Feb 8, 2024
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 @@ -42,18 +42,21 @@
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 {
Expand All @@ -62,17 +65,20 @@ private static class JsonMessage implements CloudEventReader {
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
Expand All @@ -92,6 +98,9 @@ public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> w

// 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);
}
Expand Down Expand Up @@ -257,7 +266,7 @@ public CloudEvent deserialize(JsonParser p, DeserializationContext ctxt) throws
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...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static SimpleModule getCloudEventJacksonModule(JsonFormatOptions options)
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -61,11 +64,14 @@ public boolean isForceIgnoreInvalidExtensionNameDeserialization() {
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;
Expand All @@ -87,12 +93,18 @@ public JsonFormatOptionsBuilder forceIgnoreInvalidExtensionNameDeserialization(b
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
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Loading