feat: expose a mode and version agnostic event receiver (#120)

Event receivers in the wild may not always know what version or mode an
incoming event is. Instead of requiring developers to inspect the headers
themselves, the SDK should provide an HTTP receiver that is capable of
figuring out what the version and mode (structured/binary) of an incoming
event is and handle it appropriately.

In determining the best way to expose this, I chose to modify the API a
little bit. Now, instead of `const CloudEvent = require('cloudevents-sdk');`
users need to destructure it.

```js
const { HTTPReceiver, CloudEvent } = require('cloudevents-sdk');
```

This change should not be backported to 1.x.

Fixes: https://github.com/cloudevents/sdk-javascript/issues/93

Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
Lance Ball 2020-05-06 13:25:16 -04:00 committed by GitHub
parent d9e9ae6bdc
commit 54f242b79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 5 deletions

View File

@ -1,3 +1,7 @@
const CloudEvent = require("./lib/cloudevent.js");
const HTTPReceiver = require("./lib/bindings/http/http_receiver.js");
module.exports = CloudEvent;
module.exports = {
CloudEvent,
HTTPReceiver
};

View File

@ -0,0 +1,61 @@
const V03Binary = require("./receiver_binary_0_3");
const V03Structured = require("./receiver_structured_0_3.js");
const V1Binary = require("./receiver_binary_1.js");
const V1Structured = require("./receiver_structured_1.js");
const constants = require("./constants");
class HTTPReceiver {
constructor() {
this.receivers = {
v1: {
structured: new V1Structured(),
binary: new V1Binary()
},
v03: {
structured: new V03Structured(),
binary: new V03Binary()
}
};
}
accept(headers, body) {
const mode = getMode(headers);
const version = getVersion(mode, headers, body);
switch (version) {
case constants.SPEC_V1:
return this.receivers.v1[mode].parse(body, headers);
case constants.SPEC_V03:
return this.receivers.v03[mode].parse(body, headers);
default:
console.error(
`Unknown spec version ${version}. Default to ${constants.SPEC_V1}`);
return this.receivers.v1[mode].parse(body, headers);
}
}
}
function getMode(headers) {
let mode = "binary";
const contentType = headers[constants.HEADER_CONTENT_TYPE];
if (contentType && contentType.startsWith(constants.MIME_CE)) {
mode = "structured";
}
return mode;
}
function getVersion(mode, headers, body) {
let version = constants.SPEC_V1; // default to 1.0
if (mode === "binary") {
// Check the headers for the version
const versionHeader = headers[constants.DEFAULT_SPEC_VERSION_HEADER];
if (versionHeader) { version = versionHeader; }
} else {
// structured mode - the version is in the body
version = body instanceof String
? JSON.parse(body).specversion : body.specversion;
}
return version;
}
module.exports = HTTPReceiver;

View File

@ -0,0 +1,90 @@
const { expect } = require("chai");
const { CloudEvent, HTTPReceiver } = require("../../../index.js");
const constants = require("../../../lib/bindings/http/constants.js");
const receiver = new HTTPReceiver();
const id = "1234";
const type = "org.cncf.cloudevents.test";
const source = "urn:event:from:myapi/resourse/123";
const data = {
lunch: "sushi"
};
describe("HTTP Transport Binding Receiver for CloudEvents", () => {
describe("V1", () => {
const specversion = "1.0";
it("Structured data returns a CloudEvent", () => {
const payload = {
id,
type,
source,
data,
specversion
};
const headers = {
[constants.HEADER_CONTENT_TYPE]: constants.MIME_CE_JSON
};
const event = receiver.accept(headers, payload);
validateEvent(event, specversion);
});
it("Binary data returns a CloudEvent", () => {
const headers = {
[constants.HEADER_CONTENT_TYPE]: constants.DEFAULT_CONTENT_TYPE,
[constants.DEFAULT_SPEC_VERSION_HEADER]: specversion,
[constants.BINARY_HEADERS_1.ID]: id,
[constants.BINARY_HEADERS_1.TYPE]: type,
[constants.BINARY_HEADERS_1.SOURCE]: source
};
const event = receiver.accept(headers, data);
validateEvent(event, specversion);
});
});
describe("V03", () => {
const specversion = "0.3";
it("Structured data returns a CloudEvent", () => {
const payload = {
id,
type,
source,
data,
specversion
};
const headers = {
[constants.HEADER_CONTENT_TYPE]: constants.MIME_CE_JSON
};
const event = receiver.accept(headers, payload);
validateEvent(event, specversion);
});
it("Binary data returns a CloudEvent", () => {
const headers = {
[constants.HEADER_CONTENT_TYPE]: constants.DEFAULT_CONTENT_TYPE,
[constants.DEFAULT_SPEC_VERSION_HEADER]: specversion,
[constants.BINARY_HEADERS_03.ID]: id,
[constants.BINARY_HEADERS_03.TYPE]: type,
[constants.BINARY_HEADERS_03.SOURCE]: source
};
const event = receiver.accept(headers, data);
validateEvent(event, specversion);
});
});
});
function validateEvent(event, specversion) {
expect(event instanceof CloudEvent).to.equal(true);
expect(event.getId()).to.equal(id);
expect(event.getType()).to.equal(type);
expect(event.getSource()).to.equal(source);
expect(event.getData()).to.deep.equal(data);
expect(event.getSpecversion()).to.equal(specversion);
}

View File

@ -1,6 +1,6 @@
const expect = require("chai").expect;
const v1 = require("../../../v1/index.js");
const CloudEvent = require("../../../index.js");
const { CloudEvent } = require("../../../index.js");
const { asBase64 } = require("../../../lib/utils/fun.js");

View File

@ -1,6 +1,6 @@
const expect = require("chai").expect;
const Unmarshaller = require("../../../lib/bindings/http/unmarshaller_0_3.js");
const CloudEvent = require("../../../index.js");
const { CloudEvent } = require("../../../index.js");
const v03 = require("../../../v03/index.js");
const type = "com.github.pull.create";

View File

@ -1,6 +1,6 @@
const expect = require("chai").expect;
const Spec03 = require("../lib/specs/spec_0_3.js");
const CloudEvent = require("../index.js");
const { CloudEvent } = require("../index.js");
const { v4: uuidv4 } = require("uuid");
const id = uuidv4();

View File

@ -1,6 +1,6 @@
const expect = require("chai").expect;
const Spec1 = require("../lib/specs/spec_1.js");
const CloudEvent = require("../index.js");
const { CloudEvent } = require("../index.js");
const { v4: uuidv4 } = require("uuid");
const { asBase64 } = require("../lib/utils/fun.js");