Skip to content

Fix not to render optional fields when unset #55

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

Merged
merged 8 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
10 changes: 4 additions & 6 deletions src/main/java/dev/cdevents/CDEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ private CDEvents() {
*/
public static String cdEventAsJson(CDEvent cdEvent) {
try {
if (!validateCDEvent(cdEvent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - I think this is fine, perhaps in future, we might add a flag to make validation optional.
In the golang SDK I did not include validation in the JSON rendering, I made a validate method available instead, so that it is possible to render and invalid CDEvent to JSON, and the producer can decide whether to validate or not.

I did include a call to validate in the cloudevents binding to enforce sending of valid events thought (similar to what you had before).

I wonder whether instead of moving the validation from cdEventAsCloudEvent to cdEventAsJson, it would be better to add a call to validate in the test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also ok with keeping it as it is today.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, even this keeps the behavior consistent.
Reverting to keep the validation only for CloudEvents binding, removed for JSON render and added test assertion to validate CDEvent against schema.

log.error("CDEvent validation failed against schema URL - {}", cdEvent.schemaURL());
throw new CDEventsException("CDEvent validation failed against schema URL - " + cdEvent.schemaURL());
}
return objectMapper.writeValueAsString(cdEvent);
} catch (JsonProcessingException e) {
log.error("Error while mapping cdEvent as Json {}", e.getMessage());
Expand All @@ -50,10 +54,6 @@ public static String cdEventAsJson(CDEvent cdEvent) {
* @return CloudEvent
*/
public static CloudEvent cdEventAsCloudEvent(CDEvent cdEvent) {
if (!validateCDEvent(cdEvent)) {
log.error("CDEvent validation failed against schema URL - {}", cdEvent.schemaURL());
throw new CDEventsException("CDEvent validation failed against schema URL - " + cdEvent.schemaURL());
}
String cdEventJson = cdEventAsJson(cdEvent);
log.info("CDEvent with type {} as json - {}", cdEvent.getContext().getType(), cdEventJson);
CloudEvent ceToSend = new CloudEventBuilder()
Expand All @@ -74,8 +74,6 @@ public static CloudEvent cdEventAsCloudEvent(CDEvent cdEvent) {
* @return valid cdEvent
*/
public static boolean validateCDEvent(CDEvent cdEvent) {
String cdEventJson = cdEventAsJson(cdEvent);

JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
JsonSchema jsonSchema = factory.getSchema(cdEvent.eventSchema());

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/dev/cdevents/config/CustomObjectMapper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.cdevents.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
Expand All @@ -15,6 +16,7 @@ public ObjectMapper customConfiguration() {
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.registerModule(new JavaTimeModule());
}
}
183 changes: 180 additions & 3 deletions src/test/java/dev/cdevents/CDEventsTest.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
package dev.cdevents;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL;
import dev.cdevents.config.CustomObjectMapper;
import dev.cdevents.constants.CDEventConstants;
import dev.cdevents.events.*;
import dev.cdevents.exception.CDEventsException;
import io.cloudevents.CloudEvent;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.*;

public class CDEventsTest {

@Test
void createPipelineRunFinishedEventAsCloudEvent() {
private static ObjectMapper objectMapper = new CustomObjectMapper().customConfiguration();

@Test
void createPipelineRunFinishedEventAsCloudEvent() throws IOException {
PipelineRunFinishedCDEvent cdEvent = new PipelineRunFinishedCDEvent();
cdEvent.setSource(URI.create("http://dev.cdevents"));

Expand All @@ -42,6 +48,39 @@ void createPipelineRunFinishedEventAsCloudEvent() {

}

@Test
void createPipelineRunFinishedEventOptionalFieldsUnset() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("/pipelinerun_finished_optional.json");

JsonNode expectedJsonNode = objectMapper.readTree(inputStream);
JsonNode expectedContextNode = expectedJsonNode.get("context");
JsonNode expectedSubjectNode = expectedJsonNode.get("subject");

PipelineRunFinishedCDEvent cdEvent = new PipelineRunFinishedCDEvent();
cdEvent.setSource(URI.create(expectedContextNode.get("source").asText()));
cdEvent.setSubjectId(expectedSubjectNode.get("id").asText());

String cdEventJson = CDEvents.cdEventAsJson(cdEvent);
JsonNode cdEventJsonNode = objectMapper.readTree(cdEventJson);
JsonNode cdEventContextNode = cdEventJsonNode.get("context");
JsonNode cdEventSubjectNode = cdEventJsonNode.get("subject");

//assert context and subject mandatory fields
assertThat(cdEventContextNode.get("type").asText()).isEqualTo(expectedContextNode.get("type").asText());
assertThat(cdEventContextNode.get("source").asText()).isEqualTo(expectedContextNode.get("source").asText());
assertEquals(expectedSubjectNode, cdEventSubjectNode);
assertThat(cdEventSubjectNode.get("id").asText()).isEqualTo(expectedSubjectNode.get("id").asText());
assertThat(cdEventSubjectNode.get("type").asText()).isEqualTo(expectedSubjectNode.get("type").asText());

//assert Optional field Subject Source, Content pipelineName, url, outcome, errors are set to null
assertThat(cdEventSubjectNode.get("source")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("pipelineName")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("url")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("outcome")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("errors")).isEqualTo(null);

}

@Test
void testInvalidPipelineRunFinishedEventWithNoSubject() {
PipelineRunFinishedCDEvent cdEvent = new PipelineRunFinishedCDEvent();
Expand Down Expand Up @@ -90,6 +129,38 @@ void testInvalidPipelineRunQueuedEventWithNoSubject() {
assertThat(exception.getMessage()).isEqualTo(expectedError);
}

@Test
void createPipelineRunQueuedEventWithOptionalFieldsUnset() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("/pipelinerun_queued_optional.json");

JsonNode expectedJsonNode = objectMapper.readTree(inputStream);
JsonNode expectedContextNode = expectedJsonNode.get("context");
JsonNode expectedSubjectNode = expectedJsonNode.get("subject");

PipelineRunQueuedCDEvent cdEvent = new PipelineRunQueuedCDEvent();
cdEvent.setSource(URI.create(expectedContextNode.get("source").asText()));
cdEvent.setSubjectId(expectedSubjectNode.get("id").asText());

String cdEventJson = CDEvents.cdEventAsJson(cdEvent);
JsonNode cdEventJsonNode = objectMapper.readTree(cdEventJson);
JsonNode cdEventContextNode = cdEventJsonNode.get("context");
JsonNode cdEventSubjectNode = cdEventJsonNode.get("subject");

//assert context and subject mandatory fields
assertThat(cdEventContextNode.get("type").asText()).isEqualTo(expectedContextNode.get("type").asText());
assertThat(cdEventContextNode.get("source").asText()).isEqualTo(expectedContextNode.get("source").asText());
assertEquals(expectedSubjectNode, cdEventSubjectNode);
assertThat(cdEventSubjectNode.get("id").asText()).isEqualTo(expectedSubjectNode.get("id").asText());
assertThat(cdEventSubjectNode.get("type").asText()).isEqualTo(expectedSubjectNode.get("type").asText());

//assert Optional field Subject Source, pipelineName, url is set to null
assertThat(cdEventSubjectNode.get("source")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("pipelineName")).isEqualTo(null);
assertThat(expectedSubjectNode.get("content").get("url")).isEqualTo(null);


}

@Test
void testPipelineRunQueuedEventWithCustomData() throws JsonProcessingException {
PipelineRunQueuedCDEvent cdEvent = new PipelineRunQueuedCDEvent();
Expand Down Expand Up @@ -151,6 +222,41 @@ void testInvalidPipelineRunStartedEventWithNoSubject() {
assertThat(exception.getMessage()).isEqualTo(expectedError);
}

@Test
void createPipelineRunStartedJsonEventWithOptionalFieldsUnset() throws IOException {

InputStream inputStream = getClass().getResourceAsStream("/pipelinerun_started_optional.json");

JsonNode expectedJsonNode = objectMapper.readTree(inputStream);
JsonNode expectedContextNode = expectedJsonNode.get("context");
JsonNode expectedSubjectNode = expectedJsonNode.get("subject");

PipelineRunStartedCDEvent cdEvent = new PipelineRunStartedCDEvent();
cdEvent.setSource(URI.create(expectedContextNode.get("source").asText()));

cdEvent.setSubjectId(expectedSubjectNode.get("id").asText());
cdEvent.setSubjectPipelineName(expectedSubjectNode.get("content").get("pipelineName").asText());
cdEvent.setSubjectUrl(URI.create(expectedSubjectNode.get("content").get("url").asText()));

String cdEventJson = CDEvents.cdEventAsJson(cdEvent);
JsonNode cdEventJsonNode = objectMapper.readTree(cdEventJson);
JsonNode cdEventContextNode = cdEventJsonNode.get("context");
JsonNode cdEventSubjectNode = cdEventJsonNode.get("subject");

//assert context and subject mandatory fields
assertThat(cdEventContextNode.get("type").asText()).isEqualTo(expectedContextNode.get("type").asText());
assertThat(cdEventContextNode.get("source").asText()).isEqualTo(expectedContextNode.get("source").asText());
assertEquals(expectedSubjectNode, cdEventSubjectNode);
assertThat(cdEventSubjectNode.get("id").asText()).isEqualTo(expectedSubjectNode.get("id").asText());
assertThat(cdEventSubjectNode.get("type").asText()).isEqualTo(expectedSubjectNode.get("type").asText());
assertThat(cdEventSubjectNode.get("content").get("pipelineName").asText()).isEqualTo(expectedSubjectNode.get("content").get("pipelineName").asText());
assertThat(cdEventSubjectNode.get("content").get("url").asText()).isEqualTo(expectedSubjectNode.get("content").get("url").asText());

//assert Optional field Subject Source is set to null
assertThat(cdEventSubjectNode.get("source")).isEqualTo(null);

}


@Test
void createTaskRunStartedEventAsCloudEvent() {
Expand Down Expand Up @@ -189,6 +295,41 @@ void testInvalidTaskRunStartedEventWithNoSubject() {
assertThat(exception.getMessage()).isEqualTo(expectedError);
}

@Test
void createTaskRunStartedEventJsonWithOptionalFieldsUnset() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("/taskrun_started_optional.json");

JsonNode expectedJsonNode = objectMapper.readTree(inputStream);
JsonNode expectedContextNode = expectedJsonNode.get("context");
JsonNode expectedSubjectNode = expectedJsonNode.get("subject");

TaskRunStartedCDEvent cdEvent = new TaskRunStartedCDEvent();
cdEvent.setSource(URI.create(expectedContextNode.get("source").asText()));

cdEvent.setSubjectId(expectedSubjectNode.get("id").asText());
cdEvent.setSubjectPipelineRunId(expectedSubjectNode.get("content").get("pipelineRun").get("id").asText());

String cdEventJson = CDEvents.cdEventAsJson(cdEvent);
JsonNode cdEventJsonNode = objectMapper.readTree(cdEventJson);
JsonNode cdEventContextNode = cdEventJsonNode.get("context");
JsonNode cdEventSubjectNode = cdEventJsonNode.get("subject");

//assert context and subject mandatory fields
assertThat(cdEventContextNode.get("type").asText()).isEqualTo(expectedContextNode.get("type").asText());
assertThat(cdEventContextNode.get("source").asText()).isEqualTo(expectedContextNode.get("source").asText());
assertEquals(expectedSubjectNode, cdEventSubjectNode);
assertThat(cdEventSubjectNode.get("id").asText()).isEqualTo(expectedSubjectNode.get("id").asText());
assertThat(cdEventSubjectNode.get("type").asText()).isEqualTo(expectedSubjectNode.get("type").asText());
assertThat(cdEventSubjectNode.get("content").get("pipelineRun").get("id").asText()).isEqualTo(expectedSubjectNode.get("content").get("pipelineRun").get("id").asText());

//assert Optional fields Subject Source, Content taskName, URL and pipelineRun Source are set to null
assertThat(cdEventSubjectNode.get("source")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("taskName")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("url")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("pipelineRun").get("source")).isEqualTo(null);

}

@Test
void createTaskRunFinishedEventAsCloudEvent() {

Expand All @@ -215,6 +356,42 @@ void createTaskRunFinishedEventAsCloudEvent() {
assertThat(ceDataJson).isEqualTo(cdEventJson);
}

@Test
void createTaskRunFinishedEventJsonWithOptionalFieldsUnset() throws IOException {

InputStream inputStream = getClass().getResourceAsStream("/taskrun_finished_optional.json");

JsonNode expectedJsonNode = objectMapper.readTree(inputStream);
JsonNode expectedContextNode = expectedJsonNode.get("context");
JsonNode expectedSubjectNode = expectedJsonNode.get("subject");

TaskRunFinishedCDEvent cdEvent = new TaskRunFinishedCDEvent();
cdEvent.setSource(URI.create(expectedContextNode.get("source").asText()));
cdEvent.setSubjectId(expectedSubjectNode.get("id").asText());
cdEvent.setSubjectPipelineRunId(expectedSubjectNode.get("content").get("pipelineRun").get("id").asText());

String cdEventJson = CDEvents.cdEventAsJson(cdEvent);
JsonNode cdEventJsonNode = objectMapper.readTree(cdEventJson);
JsonNode cdEventContextNode = cdEventJsonNode.get("context");
JsonNode cdEventSubjectNode = cdEventJsonNode.get("subject");

//assert context and subject mandatory fields
assertThat(cdEventContextNode.get("type").asText()).isEqualTo(expectedContextNode.get("type").asText());
assertThat(cdEventContextNode.get("source").asText()).isEqualTo(expectedContextNode.get("source").asText());
assertEquals(expectedSubjectNode, cdEventSubjectNode);
assertThat(cdEventSubjectNode.get("id").asText()).isEqualTo(expectedSubjectNode.get("id").asText());
assertThat(cdEventSubjectNode.get("type").asText()).isEqualTo(expectedSubjectNode.get("type").asText());
assertThat(cdEventSubjectNode.get("content").get("pipelineRun").get("id").asText()).isEqualTo(expectedSubjectNode.get("content").get("pipelineRun").get("id").asText());

//assert Optional fields Subject Source, Content taskName, URL, outcome, errors and pipelineRun Source are set to null
assertThat(cdEventSubjectNode.get("source")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("taskName")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("url")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("outcome")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("errors")).isEqualTo(null);
assertThat(cdEventSubjectNode.get("content").get("pipelineRun").get("source")).isEqualTo(null);
}

@Test
void testInvalidTaskRunFinishedEventWithNoSubject() {
TaskRunFinishedCDEvent cdEvent = new TaskRunFinishedCDEvent();
Expand Down
18 changes: 18 additions & 0 deletions src/test/resources/pipelinerun_finished_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"context": {
"id": "123456789",
"type": "dev.cdevents.pipelinerun.finished.0.1.0",
"source": "http://dev.cdevents",
"version": "0.1.2",
"timestamp": "2023-05-24T11:17:06Z"
},
"customData": {
},
"customDataContentType": "application/json",
"subject": {
"id": "/dev/pipeline/run/subject",
"type": "PIPELINERUN",
"content": {
}
}
}
18 changes: 18 additions & 0 deletions src/test/resources/pipelinerun_queued_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"context": {
"id": "dc0bf5f5-f2e4-44c3-b9a6-1f0cf51b050b",
"type": "dev.cdevents.pipelinerun.queued.0.1.0",
"source": "http://dev.cdevents",
"version": "0.1.2",
"timestamp": "2023-05-24T18:34:41Z"
},
"customData": {
},
"customDataContentType": "application/json",
"subject": {
"id": "/dev/pipeline/run/subject",
"type": "PIPELINERUN",
"content": {
}
}
}
20 changes: 20 additions & 0 deletions src/test/resources/pipelinerun_started_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"context": {
"id": "cbde772c-17ad-490d-aea0-41fb060d1483",
"type": "dev.cdevents.pipelinerun.started.0.1.0",
"source": "http://dev.cdevents",
"version": "0.1.2",
"timestamp": "2023-05-24T18:48:12Z"
},
"customData": {
},
"customDataContentType": "application/json",
"subject": {
"id": "/dev/pipeline/run/subject",
"type": "PIPELINERUN",
"content": {
"pipelineName": "test-pipeline-started",
"url": "http://dev/pipeline/url"
}
}
}
21 changes: 21 additions & 0 deletions src/test/resources/taskrun_finished_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"context": {
"id": "49e16403-c5b5-4e2a-9540-160787459a21",
"type": "dev.cdevents.taskrun.finished.0.1.0",
"source": "http://dev.cdevents",
"version": "0.1.2",
"timestamp": "2023-05-24T19:20:57Z"
},
"customData": {
},
"customDataContentType": "application/json",
"subject": {
"id": "/dev/task/run/subject",
"type": "TASKRUN",
"content": {
"pipelineRun": {
"id": "/dev/pipeline/run/subject"
}
}
}
}
21 changes: 21 additions & 0 deletions src/test/resources/taskrun_started_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"context": {
"id": "f75eb164-947c-4827-9036-9ec7fda38873",
"type": "dev.cdevents.taskrun.started.0.1.0",
"source": "http://dev.cdevents",
"version": "0.1.2",
"timestamp": "2023-05-24T19:16:11Z"
},
"customData": {
},
"customDataContentType": "application/json",
"subject": {
"id": "/dev/task/run/subject",
"type": "TASKRUN",
"content": {
"pipelineRun": {
"id": "/dev/pipeline/run/subject"
}
}
}
}