Skip to content

Commit

Permalink
fix: do not require an HTTP body on incoming binary event messages
Browse files Browse the repository at this point in the history
This commit modifies the HTTP receivers/parsers to allow for the incoming body
of an HTTP request to be empty if the event message is sent using the binary
mode. In structured mode, a `ValidationError` will still be thrown, since the
entire event must be encoded in the HTTP body.

Signed-off-by: Lance Ball <lball@redhat.com>
  • Loading branch information
lance committed Jul 23, 2020
1 parent d866691 commit a7c326b
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 35 deletions.
19 changes: 12 additions & 7 deletions src/transport/http/binary_receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ export class BinaryHTTPReceiver {
* @returns {CloudEvent} an instance of CloudEvent representing the incoming request
* @throws {ValidationError} of the event does not conform to the spec
*/
parse(payload: string | Record<string, unknown>, headers: Headers): CloudEvent {
if (!payload) throw new ValidationError("payload is null or undefined");
parse(payload: string | Record<string, unknown> | undefined | null, headers: Headers): CloudEvent {
if (!headers) throw new ValidationError("headers is null or undefined");
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
if (payload) {
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
}

if (
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] &&
Expand All @@ -61,11 +62,15 @@ export class BinaryHTTPReceiver {
}
}

const parser = parserByContentType[eventObj.datacontenttype as string];
if (!parser) {
throw new ValidationError(`no parser found for content type ${eventObj.datacontenttype}`);
let parsedPayload;

if (payload) {
const parser = parserByContentType[eventObj.datacontenttype as string];
if (!parser) {
throw new ValidationError(`no parser found for content type ${eventObj.datacontenttype}`);
}
parsedPayload = parser.parse(payload);
}
const parsedPayload = parser.parse(payload);

// Every unprocessed header can be an extension
for (const header in sanitizedHeaders) {
Expand Down
2 changes: 1 addition & 1 deletion src/transport/http/structured_receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class StructuredHTTPReceiver {
* @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload
* @throws {ValidationError} if the payload and header combination do not conform to the spec
*/
parse(payload: Record<string, unknown> | string, headers: Headers): CloudEvent {
parse(payload: Record<string, unknown> | string | undefined | null, headers: Headers): CloudEvent {
if (!payload) throw new ValidationError("payload is null or undefined");
if (!headers) throw new ValidationError("headers is null or undefined");
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
Expand Down
9 changes: 6 additions & 3 deletions src/transport/receiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export class Receiver {
* @param {Object|JSON} body The body of the HTTP request
* @return {CloudEvent} A new {CloudEvent} instance
*/
accept(headers: Headers, body: string | Record<string, unknown> | CloudEventV1 | CloudEventV03): CloudEvent {
accept(
headers: Headers,
body: string | Record<string, unknown> | CloudEventV1 | CloudEventV03 | undefined | null,
): CloudEvent {
const cleanHeaders: Headers = sanitize(headers);
const mode: Mode = getMode(cleanHeaders);
const version = getVersion(mode, cleanHeaders, body);
Expand Down Expand Up @@ -103,7 +106,7 @@ function getMode(headers: Headers): Mode {
function getVersion(
mode: Mode,
headers: Headers,
body: string | Record<string, unknown> | CloudEventV03 | CloudEventV1,
body: string | Record<string, unknown> | CloudEventV03 | CloudEventV1 | undefined | null,
) {
if (mode === Mode.BINARY) {
// Check the headers for the version
Expand All @@ -113,7 +116,7 @@ function getVersion(
}
} else {
// structured mode - the version is in the body
return typeof body === "string" ? JSON.parse(body).specversion : body.specversion;
return typeof body === "string" ? JSON.parse(body).specversion : (body as CloudEvent).specversion;
}
return Version.V1;
}
24 changes: 24 additions & 0 deletions test/integration/http_receiver_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
});

it("Accepts binary events when the data property is undefined", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = receiver.accept(binaryHeaders, undefined);
expect(event.data).to.be.undefined;
});

it("Accepts binary events when the data property is null", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = receiver.accept(binaryHeaders, null);
expect(event.data).to.be.undefined;
});

it("Converts the JSON body of a structured event to an Object", () => {
const payload = {
id,
Expand Down
12 changes: 0 additions & 12 deletions test/integration/receiver_binary_03_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@ const receiver = new BinaryHTTPReceiver(Version.V03);

describe("HTTP Transport Binding Binary Receiver for CloudEvents v0.3", () => {
describe("Check", () => {
it("Throw error when payload arg is null or undefined", () => {
// setup
const payload = undefined;
const attributes = {};

// act and assert
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
ValidationError,
"payload is null or undefined",
);
});

it("Throw error when attributes arg is null or undefined", () => {
// setup
const payload = {};
Expand Down
12 changes: 0 additions & 12 deletions test/integration/receiver_binary_1_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@ const receiver = new BinaryHTTPReceiver(Version.V1);

describe("HTTP Transport Binding Binary Receiver for CloudEvents v1.0", () => {
describe("Check", () => {
it("Throw error when payload arg is null or undefined", () => {
// setup
const payload = null;
const attributes = {};

// act and assert
expect(receiver.parse.bind(receiver, (payload as unknown) as string, attributes)).to.throw(
ValidationError,
"payload is null or undefined",
);
});

it("Throw error when attributes arg is null or undefined", () => {
// setup
const payload = {};
Expand Down

0 comments on commit a7c326b

Please sign in to comment.