feat!: expose a version agnostic event emitter (#141)

* feat!: expose a version agnostic event emitter

This is a breaking change.

This commit exposes an HTTP based event emitter that simplifes the API.
To use it, simply import the SDK and start emitting. The default spec
version is 1.0, but you can use 0.3 by supplying that to the constructor.

By default, CloudEvents are emitted in binary mode, but this can be changed
by providing the "structured" parameter to the `send()` function.

This commit also eliminates the version specific emitters and receivers
from the `v1` and `v03` exports, and eliminates the explicit usage of
versioned emitters from `lib/bindings/http`.

Finally, the CE headers can be retrieved from the emitter for a given
event by passing the event to the `headers()` function.

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

Signed-off-by: Lance Ball <lball@redhat.com>
This commit is contained in:
Lance Ball 2020-05-14 11:37:02 -04:00 committed by GitHub
parent 76659697aa
commit 250a0a144c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 591 additions and 325 deletions

View File

@ -62,25 +62,60 @@ console.log(receivedEvent.format());
#### Emitting Events
Currently, to emit events, you'll need to decide whether the event is in
To emit events, you'll need to decide whether the event should be sent in
binary or structured format, and determine what version of the CloudEvents
specification you want to send the event as.
By default, the `HTTPEmitter` will emit events over HTTP POST using the
1.0 specification, in binary mode. You can emit 0.3 events by providing
the specication version in the constructor to `HTTPEmitter`. To send
structured events, add that string as a parameter to `emitter.sent()`.
```js
const { CloudEvent } = require("cloudevents-sdk");
const { StructuredHTTPEmitter } = require("cloudevents-sdk/v1");
const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk");
const myevent = new CloudEvent()
.type("com.github.pull.create")
.source("urn:event:from:myapi/resource/123");
// With only an endpoint URL, this creates a v1 emitter
const v1Emitter = new HTTPEmitter({
url: "https://cloudevents.io/example"
});
const event = new CloudEvent()
.type(type)
.source(source)
.time(new Date())
.data(data)
const emitter = new StructuredHTTPEmitter({
method: "POST",
url : "https://myserver.com"
// By default, the emitter will send binary events
v1Emitter.send(event).then((response) => {
// handle the response
}).catch(console.error);
// To send a structured event, just add that as an option
v1Emitter.send(event, { mode: "structured" })
.then((response) => {
// handle the response
}).catch(console.error);
// To send an event to an alternate URL, add that as an option
v1Emitter.send(event, { url: "https://alternate.com/api" })
.then((response) => {
// handle the response
}).catch(console.error);
// Sending a v0.3 event works the same, just let the emitter know when
// you create it that you are working with the 0.3 spec
const v03Emitter = new HTTPEmitter({
url: "https://cloudevents.io/example",
version: "0.3"
});
// Emit the event
emitter.emit(myevent)
// Again, the default is to send binary events
// To send a structured event or to an alternate URL, provide those
// as parameters in a options object as above
v3Emitter.send(event)
.then((response) => {
// handle the response
}).catch(console.error);
```
## Supported specification features

View File

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

View File

@ -1,29 +1,64 @@
const axios = require("axios");
const Constants = require("./constants.js");
const defaults = {};
defaults[Constants.HEADERS] = {};
defaults[Constants.HEADERS][Constants.HEADER_CONTENT_TYPE] =
Constants.DEFAULT_CONTENT_TYPE;
const {
HEADERS,
BINARY_HEADERS_03,
BINARY_HEADERS_1,
HEADER_CONTENT_TYPE,
DEFAULT_CONTENT_TYPE,
DATA_ATTRIBUTE,
SPEC_V1,
SPEC_V03
} = require("./constants.js");
function BinaryHTTPEmitter(config, headerByGetter, extensionPrefix) {
this.config = Object.assign({}, defaults, config);
this.headerByGetter = headerByGetter;
this.extensionPrefix = extensionPrefix;
}
const defaults = {
[HEADERS]: {
[HEADER_CONTENT_TYPE]: DEFAULT_CONTENT_TYPE
},
method: "POST"
};
BinaryHTTPEmitter.prototype.emit = function(cloudevent) {
const config = Object.assign({}, this.config);
const headers = Object.assign({}, this.config[Constants.HEADERS]);
/**
* A class to emit binary CloudEvents over HTTP.
*/
class BinaryHTTPEmitter {
/**
* Create a new {BinaryHTTPEmitter} for the provided CloudEvent specification version.
* Once an instance is created for a given spec version, it may only be used to send
* events for that version.
* Default version is 1.0
* @param {string} version - the CloudEvent HTTP specification version.
* Default: 1.0
*/
constructor(version) {
if (version === SPEC_V1) {
this.headerByGetter = require("./emitter_binary_1.js");
this.extensionPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX;
} else if (version === SPEC_V03) {
this.headerByGetter = require("./emitter_binary_0_3.js");
this.extensionPrefix = BINARY_HEADERS_03.EXTENSIONS_PREFIX;
}
}
/**
* Sends this cloud event to a receiver over HTTP.
*
* @param {Object} options The configuration options for this event. Options
* provided other than `url` will be passed along to Node.js `http.request`.
* https://nodejs.org/api/http.html#http_http_request_options_callback
* @param {URL} options.url The HTTP/S url that should receive this event
* @param {Object} cloudevent the CloudEvent to be sent
* @returns {Promise} Promise with an eventual response from the receiver
*/
async emit(options, cloudevent) {
const config = { ...options, ...defaults };
const headers = config[HEADERS];
Object.keys(this.headerByGetter)
.filter((getter) => cloudevent[getter]())
.forEach((getter) => {
const header = this.headerByGetter[getter];
headers[header.name] =
header.parser(
cloudevent[getter]()
);
headers[header.name] = header.parser(cloudevent[getter]());
});
// Set the cloudevent payload
@ -39,11 +74,12 @@ BinaryHTTPEmitter.prototype.emit = function(cloudevent) {
headers[this.extensionPrefix + ext] = exts[ext];
});
config[Constants.DATA_ATTRIBUTE] = data;
config[DATA_ATTRIBUTE] = data;
config.headers = headers;
// Return the Promise
return axios.request(config);
};
}
}
module.exports = BinaryHTTPEmitter;

View File

@ -1,64 +1,53 @@
const BinaryHTTPEmitter = require("./emitter_binary.js");
const Constants = require("./constants.js");
const {
HEADER_CONTENT_TYPE,
BINARY_HEADERS_03
} = require("./constants.js");
const headerByGetter = {};
headerByGetter.getDataContentType = {
name: Constants.HEADER_CONTENT_TYPE,
name: HEADER_CONTENT_TYPE,
parser: (v) => v
};
headerByGetter.getDataContentEncoding = {
name: Constants.BINARY_HEADERS_03.CONTENT_ENCONDING,
name: BINARY_HEADERS_03.CONTENT_ENCONDING,
parser: (v) => v
};
headerByGetter.getSubject = {
name: Constants.BINARY_HEADERS_03.SUBJECT,
name: BINARY_HEADERS_03.SUBJECT,
parser: (v) => v
};
headerByGetter.getType = {
name: Constants.BINARY_HEADERS_03.TYPE,
name: BINARY_HEADERS_03.TYPE,
parser: (v) => v
};
headerByGetter.getSpecversion = {
name: Constants.BINARY_HEADERS_03.SPEC_VERSION,
name: BINARY_HEADERS_03.SPEC_VERSION,
parser: (v) => v
};
headerByGetter.getSource = {
name: Constants.BINARY_HEADERS_03.SOURCE,
name: BINARY_HEADERS_03.SOURCE,
parser: (v) => v
};
headerByGetter.getId = {
name: Constants.BINARY_HEADERS_03.ID,
name: BINARY_HEADERS_03.ID,
parser: (v) => v
};
headerByGetter.getTime = {
name: Constants.BINARY_HEADERS_03.TIME,
name: BINARY_HEADERS_03.TIME,
parser: (v) => v
};
headerByGetter.getSchemaurl = {
name: Constants.BINARY_HEADERS_03.SCHEMA_URL,
name: BINARY_HEADERS_03.SCHEMA_URL,
parser: (v) => v
};
function HTTPBinary(configuration) {
this.emitter = new BinaryHTTPEmitter(
configuration,
headerByGetter,
Constants.BINARY_HEADERS_03.EXTENSIONS_PREFIX
);
}
HTTPBinary.prototype.emit = function(cloudevent) {
return this.emitter.emit(cloudevent);
};
module.exports = HTTPBinary;
module.exports = headerByGetter;

View File

@ -1,59 +1,48 @@
const BinaryHTTPEmitter = require("./emitter_binary.js");
const Constants = require("./constants.js");
const {
HEADER_CONTENT_TYPE,
BINARY_HEADERS_1
} = require("./constants.js");
const headerByGetter = {};
headerByGetter.getDataContentType = {
name: Constants.HEADER_CONTENT_TYPE,
name: HEADER_CONTENT_TYPE,
parser: (v) => v
};
headerByGetter.getSubject = {
name: Constants.BINARY_HEADERS_1.SUBJECT,
name: BINARY_HEADERS_1.SUBJECT,
parser: (v) => v
};
headerByGetter.getType = {
name: Constants.BINARY_HEADERS_1.TYPE,
name: BINARY_HEADERS_1.TYPE,
parser: (v) => v
};
headerByGetter.getSpecversion = {
name: Constants.BINARY_HEADERS_1.SPEC_VERSION,
name: BINARY_HEADERS_1.SPEC_VERSION,
parser: (v) => v
};
headerByGetter.getSource = {
name: Constants.BINARY_HEADERS_1.SOURCE,
name: BINARY_HEADERS_1.SOURCE,
parser: (v) => v
};
headerByGetter.getId = {
name: Constants.BINARY_HEADERS_1.ID,
name: BINARY_HEADERS_1.ID,
parser: (v) => v
};
headerByGetter.getTime = {
name: Constants.BINARY_HEADERS_1.TIME,
name: BINARY_HEADERS_1.TIME,
parser: (v) => v
};
headerByGetter.getDataschema = {
name: Constants.BINARY_HEADERS_1.DATA_SCHEMA,
name: BINARY_HEADERS_1.DATA_SCHEMA,
parser: (v) => v
};
function HTTPBinary(configuration) {
this.emitter = new BinaryHTTPEmitter(
configuration,
headerByGetter,
Constants.BINARY_HEADERS_1.EXTENSIONS_PREFIX
);
}
HTTPBinary.prototype.emit = function(cloudevent) {
return this.emitter.emit(cloudevent);
};
module.exports = HTTPBinary;
module.exports = headerByGetter;

View File

@ -1,24 +1,38 @@
const axios = require("axios");
const {
DATA_ATTRIBUTE,
DEFAULT_CE_CONTENT_TYPE,
HEADERS,
HEADER_CONTENT_TYPE
} = require("./constants.js");
const Constants = require("./constants.js");
const defaults = {};
defaults[Constants.HEADERS] = {};
defaults[Constants.HEADERS][Constants.HEADER_CONTENT_TYPE] =
Constants.DEFAULT_CE_CONTENT_TYPE;
function StructuredHTTPEmitter(configuration) {
this.config = Object.assign({}, defaults, configuration);
}
StructuredHTTPEmitter.prototype.emit = function(cloudevent) {
// Set the cloudevent payload
this.config[Constants.DATA_ATTRIBUTE] = cloudevent.format();
// Return the Promise
return axios.request(this.config).then((response) => {
delete this.config[Constants.DATA_ATTRIBUTE];
return response;
});
const defaults = {
[HEADERS]: {
[HEADER_CONTENT_TYPE]: DEFAULT_CE_CONTENT_TYPE
},
method: "POST"
};
/**
* A class for sending {CloudEvent} instances over HTTP.
*/
class StructuredHTTPEmitter {
// TODO: Do we really need a class here? There is no state maintenance
/**
* Sends the event over HTTP
* @param {Object} options The configuration options for this event. Options
* provided will be passed along to Node.js `http.request()`.
* https://nodejs.org/api/http.html#http_http_request_options_callback
* @param {URL} options.url The HTTP/S url that should receive this event
* @param {CloudEvent} cloudevent The CloudEvent to be sent
* @returns {Promise} Promise with an eventual response from the receiver
*/
async emit(options, cloudevent) {
const config = { ...defaults, ...options };
config[DATA_ATTRIBUTE] = cloudevent.format();
return axios.request(config);
}
}
module.exports = StructuredHTTPEmitter;

View File

@ -0,0 +1,86 @@
const BinaryHTTPEmitter = require("./emitter_binary.js");
const StructuredEmitter = require("./emitter_structured.js");
const {
SPEC_V03,
SPEC_V1
} = require("./constants");
/**
* A class which is capable of sending binary and structured events using
* the CloudEvents HTTP Protocol Binding specification.
*
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md#13-content-modes
*/
class HTTPEmitter {
/**
* Creates a new instance of {HTTPEmitter}. The default emitter uses the 1.0
* protocol specification in binary mode.
*
* @param {Object} [options] The configuration options for this event emitter
* @param {URL} options.url The endpoint that will receive the sent events.
* @param {string} [options.version] The HTTP binding specification version. Default: "1.0"
* @throws {TypeError} if no options.url is provided or an unknown specification version is provided.
*/
constructor({ url, version = SPEC_V1 } = {}) {
if (version !== SPEC_V03 && version !== SPEC_V1) {
throw new TypeError(
`Unknown CloudEvent specification version: ${version}`);
}
if (!url) {
throw new TypeError("A default endpoint URL is required for a CloudEvent emitter");
}
this.binary = new BinaryHTTPEmitter(version);
this.structured = new StructuredEmitter();
this.url = url;
}
/**
* Sends the {CloudEvent} to an event receiver over HTTP POST
*
* @param {CloudEvent} event the CloudEvent to be sent
* @param {Object} [options] The configuration options for this event. Options
* provided will be passed along to Node.js `http.request()`.
* https://nodejs.org/api/http.html#http_http_request_options_callback
* @param {URL} [options.url] The HTTP/S url that should receive this event.
* The URL is optional if one was provided when this emitter was constructed.
* In that case, it will be used as the recipient endpoint. The endpoint can
* be overridden by providing a URL here.
* @param {string} [options.mode] the message mode for sending this event.
* Possible values are "binary" and "structured". Default: structured
* @returns {Promise} Promise with an eventual response from the receiver
*/
send(event, { url, mode = "binary", ...httpOpts } = {}) {
if (!url) { url = this.url; }
httpOpts.url = url;
if (mode === "binary") {
return this.binary.emit(httpOpts, event);
} else if (mode === "structured") {
return this.structured.emit(httpOpts, event);
}
throw new TypeError(`Unknown transport mode ${mode}.`);
}
/**
* Returns the HTTP headers that will be sent for this event when the HTTP transmission
* mode is "binary". Events sent over HTTP in structured mode only have a single CE header
* and that is "ce-id", corresponding to the event ID.
* @param {CloudEvent} event a CloudEvent
* @returns {Object} the headers that will be sent for the event
*/
headers(event) {
const headers = {};
Object.keys(this.binary.headerByGetter)
.filter((getter) => event[getter]())
.forEach((getter) => {
const header = this.binary.headerByGetter[getter];
headers[header.name] = header.parser(event[getter]());
});
return headers;
}
}
module.exports = HTTPEmitter;

View File

@ -0,0 +1,189 @@
const { expect } = require("chai");
const nock = require("nock");
const {
SPEC_V1,
SPEC_V03,
DEFAULT_CE_CONTENT_TYPE,
BINARY_HEADERS_03,
BINARY_HEADERS_1
} = require("../../../lib/bindings/http/constants.js");
const { CloudEvent, HTTPEmitter } = require("../../../");
const V1Spec = require("../../../v1").Spec;
const V03Spec = require("../../../v03").Spec;
const receiver = "https://cloudevents.io/";
const type = "com.example.test";
const source = "urn:event:from:myapi/resource/123";
const ext1Name = "lunch";
const ext1Value = "tacos";
const ext2Name = "supper";
const ext2Value = "sushi";
const data = {
lunchBreak: "noon"
};
describe("HTTP Transport Binding Emitter for CloudEvents", () => {
beforeEach(() => {
nock(receiver)
.post("/")
.reply(function(uri, requestBody) {
// return the request body and the headers so they can be
// examined in the test
if (typeof requestBody === "string") {
requestBody = JSON.parse(requestBody);
}
const returnBody = { ...requestBody, ...this.req.headers };
return [
201,
returnBody
];
});
});
describe("V1", () => {
const emitter = new HTTPEmitter({ url: receiver });
const event = new CloudEvent(V1Spec)
.type(type)
.source(source)
.time(new Date())
.data(data)
.addExtension(ext1Name, ext1Value)
.addExtension(ext2Name, ext2Value);
it("Sends a binary 1.0 CloudEvent by default", () => {
emitter.send(event)
.then((response) => {
// A binary message will have a ce-id header
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(event.getId());
expect(response.data[BINARY_HEADERS_1.SPEC_VERSION]).to.equal(SPEC_V1);
// A binary message will have a request body for the data
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
it("Provides the HTTP headers for a binary event", () => {
const headers = emitter.headers(event);
expect(headers[BINARY_HEADERS_1.TYPE]).to.equal(event.getType());
expect(headers[BINARY_HEADERS_1.SPEC_VERSION]).to.equal(event.getSpecversion());
expect(headers[BINARY_HEADERS_1.SOURCE]).to.equal(event.getSource());
expect(headers[BINARY_HEADERS_1.ID]).to.equal(event.getId());
expect(headers[BINARY_HEADERS_1.TIME]).to.equal(event.getTime());
});
it("Sends a structured 1.0 CloudEvent if specified", () => {
emitter.send(event, { mode: "structured" })
.then((response) => {
// A structured message will have a cloud event content type
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(response.data.specversion).to.equal(SPEC_V1);
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
it("Sends to an alternate URL if specified", () => {
nock(receiver)
.post("/alternate")
.reply(function(uri, requestBody) {
// return the request body and the headers so they can be
// examined in the test
if (typeof requestBody === "string") {
requestBody = JSON.parse(requestBody);
}
const returnBody = { ...requestBody, ...this.req.headers };
return [
201,
returnBody
];
});
emitter.send(event, { mode: "structured", url: `${receiver}alternate` })
.then((response) => {
// A structured message will have a cloud event content type
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(response.data[BINARY_HEADERS_1.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(response.data.specversion).to.equal(SPEC_V1);
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
});
describe("V03", () => {
const emitter = new HTTPEmitter({ url: receiver, version: SPEC_V03 });
const event = new CloudEvent(V03Spec)
.type(type)
.source(source)
.time(new Date())
.data(data)
.addExtension(ext1Name, ext1Value)
.addExtension(ext2Name, ext2Value);
it("Sends a binary 0.3 CloudEvent", () => {
emitter.send(event)
.then((response) => {
// A binary message will have a ce-id header
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(event.getId());
expect(response.data[BINARY_HEADERS_03.SPEC_VERSION]).to.equal(SPEC_V03);
// A binary message will have a request body for the data
expect(response.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
it("Provides the HTTP headers for a binary event", () => {
const headers = emitter.headers(event);
expect(headers[BINARY_HEADERS_03.TYPE]).to.equal(event.getType());
expect(headers[BINARY_HEADERS_03.SPEC_VERSION]).to.equal(event.getSpecversion());
expect(headers[BINARY_HEADERS_03.SOURCE]).to.equal(event.getSource());
expect(headers[BINARY_HEADERS_03.ID]).to.equal(event.getId());
expect(headers[BINARY_HEADERS_03.TIME]).to.equal(event.getTime());
});
it("Sends a structured 0.3 CloudEvent if specified", () => {
emitter.send(event, { mode: "structured", foo: "bar" })
.then((response) => {
// A structured message will have a cloud event content type
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(response.data.specversion).to.equal(SPEC_V03);
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
it("Sends to an alternate URL if specified", () => {
nock(receiver)
.post("/alternate")
.reply(function(uri, requestBody) {
// return the request body and the headers so they can be
// examined in the test
if (typeof requestBody === "string") {
requestBody = JSON.parse(requestBody);
}
const returnBody = { ...requestBody, ...this.req.headers };
return [
201,
returnBody
];
});
emitter.send(event, { mode: "structured", url: `${receiver}alternate` })
.then((response) => {
// A structured message will have a cloud event content type
expect(response.data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(response.data[BINARY_HEADERS_03.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(response.data.specversion).to.equal(SPEC_V03);
expect(response.data.data.lunchBreak).to.equal(data.lunchBreak);
}).catch(expect.fail);
});
});
});

View File

@ -1,7 +1,8 @@
const expect = require("chai").expect;
const v03 = require("../../../v03/index.js");
const ValidationError = require("../../../lib/validation_error.js");
const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured_0_3.js");
const CloudEvent = require("../../../lib/cloudevent.js");
const { Spec } = require("../../../v03/index.js");
const receiver = new HTTPStructuredReceiver();
@ -68,7 +69,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
it("Throw error data content encoding is base64, but 'data' is not",
() => {
// setup
const payload = v03.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType("text/plain")
@ -119,8 +120,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
describe("Parse", () => {
it("Throw error when the event does not follow the spec", () => {
// setup
const payload =
v03.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.time(now)
@ -140,7 +140,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
it("Should accept event that follows the spec", () => {
// setup
const id = "id-x0dk";
const payload = v03.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.id(id)
@ -170,7 +170,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
it("Should accept 'extension1'", () => {
// setup
const extension1 = "mycuston-ext1";
const payload = v03.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)
@ -195,7 +195,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => {
it("Should parse 'data' stringfied json to json object", () => {
// setup
const payload = v03.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)

View File

@ -1,5 +1,5 @@
const expect = require("chai").expect;
const v1 = require("../../../v1/index.js");
const { Spec } = require("../../../v1/index.js");
const { CloudEvent } = require("../../../index.js");
const { asBase64 } = require("../../../lib/utils/fun.js");
const ValidationError = require("../../../lib/validation_error.js");
@ -99,7 +99,7 @@ describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0",
it("Should accept event that follows the spec", () => {
// setup
const id = "id-x0dk";
const payload = v1.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.id(id)
@ -129,7 +129,7 @@ describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0",
it("Should accept 'extension1'", () => {
// setup
const extension1 = "mycustom-ext1";
const payload = v1.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)
@ -154,7 +154,7 @@ describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0",
it("Should parse 'data' stringified json to json object", () => {
// setup
const payload = v1.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)
@ -179,7 +179,7 @@ describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0",
const bindata = Uint32Array
.from(JSON.stringify(data), (c) => c.codePointAt(0));
const expected = asBase64(bindata);
const payload = v1.event()
const payload = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)

View File

@ -1,9 +1,12 @@
const expect = require("chai").expect;
const nock = require("nock");
const BinaryHTTPEmitter =
require("../lib/bindings/http/emitter_binary_0_3.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary.js");
const StructuredHTTPEmitter = require("../lib/bindings/http/emitter_structured.js");
const CloudEvent = require("../lib/cloudevent.js");
const v03 = require("../v03/index.js");
const {
SPEC_V03
} = require("../lib/bindings/http/constants.js");
const type = "com.github.pull.create";
const source = "urn:event:from:myapi/resourse/123";
@ -54,8 +57,8 @@ const httpcfg = {
url: `${webhook}/json`
};
const binary = new BinaryHTTPEmitter(httpcfg);
const structured = new v03.StructuredHTTPEmitter(httpcfg);
const binary = new BinaryHTTPEmitter(SPEC_V03);
const structured = new StructuredHTTPEmitter();
describe("HTTP Transport Binding - Version 0.3", () => {
beforeEach(() => {
@ -68,14 +71,14 @@ describe("HTTP Transport Binding - Version 0.3", () => {
describe("Structured", () => {
describe("JSON Format", () => {
it(`requires '${contentType}' Content-Type in the header`,
() => structured.emit(cloudevent)
() => structured.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(contentType);
}));
it("the request payload should be correct",
() => structured.emit(cloudevent)
() => structured.emit(httpcfg, cloudevent)
.then((response) => {
expect(JSON.parse(response.config.data))
.to.deep.equal(cloudevent.format());
@ -83,7 +86,7 @@ describe("HTTP Transport Binding - Version 0.3", () => {
describe("'data' attribute with 'base64' encoding", () => {
it("the request payload should be correct",
() => structured.emit(cebase64)
() => structured.emit(httpcfg, cebase64)
.then((response) => {
expect(JSON.parse(response.config.data).data)
.to.equal(cebase64.format().data);
@ -95,127 +98,127 @@ describe("HTTP Transport Binding - Version 0.3", () => {
describe("Binary", () => {
describe("JSON Format", () => {
it(`requires ${cloudevent.getDataContentType()} in the header`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(cloudevent.getDataContentType());
}));
it("the request payload should be correct", () => binary.emit(cloudevent)
it("the request payload should be correct", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(JSON.parse(response.config.data))
.to.deep.equal(cloudevent.getData());
}));
it("HTTP Header contains 'ce-type'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-type'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-type");
}));
it("HTTP Header contains 'ce-specversion'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-specversion'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-specversion");
}));
it("HTTP Header contains 'ce-source'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-source'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-source");
}));
it("HTTP Header contains 'ce-id'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-id'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-id");
}));
it("HTTP Header contains 'ce-time'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-time'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-time");
}));
it("HTTP Header contains 'ce-schemaurl'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-schemaurl'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-schemaurl");
}));
it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(cloudevent)
it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property(`ce-${ext1Name}`);
}));
it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(cloudevent)
it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property(`ce-${ext2Name}`);
}));
it("HTTP Header contains 'ce-subject'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-subject'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-subject");
}));
it("should 'ce-type' have the right value", () => binary.emit(cloudevent)
it("should 'ce-type' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getType())
.to.equal(response.config.headers["ce-type"]);
}));
it("should 'ce-specversion' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSpecversion())
.to.equal(response.config.headers["ce-specversion"]);
}));
it("should 'ce-source' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSource())
.to.equal(response.config.headers["ce-source"]);
}));
it("should 'ce-id' have the right value", () => binary.emit(cloudevent)
it("should 'ce-id' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getId())
.to.equal(response.config.headers["ce-id"]);
}));
it("should 'ce-time' have the right value", () => binary.emit(cloudevent)
it("should 'ce-time' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getTime())
.to.equal(response.config.headers["ce-time"]);
}));
it("should 'ce-schemaurl' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSchemaurl())
.to.equal(response.config.headers["ce-schemaurl"]);
}));
it(`should 'ce-${ext1Name}' have the right value`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getExtensions()[ext1Name])
.to.equal(response.config.headers[`ce-${ext1Name}`]);
}));
it(`should 'ce-${ext2Name}' have the right value`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getExtensions()[ext2Name])
.to.equal(response.config.headers[`ce-${ext2Name}`]);
}));
it("should 'ce-subject' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSubject())
.to.equal(response.config.headers["ce-subject"]);
@ -223,16 +226,16 @@ describe("HTTP Transport Binding - Version 0.3", () => {
describe("'data' attribute with 'base64' encoding", () => {
it("HTTP Header contains 'ce-datacontentencoding'",
() => binary.emit(cebase64)
() => binary.emit(httpcfg, cebase64)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-datacontentencoding");
}));
it("should 'ce-datacontentencoding' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cebase64)
.then((response) => {
expect(cloudevent.getDataContentEncoding())
expect(cebase64.getDataContentEncoding())
.to.equal(response.config.headers["ce-datacontentencoding"]);
}));
});

View File

@ -2,14 +2,14 @@ const expect = require("chai").expect;
const nock = require("nock");
const https = require("https");
const { asBase64 } = require("../lib/utils/fun.js");
const {
Spec,
BinaryHTTPEmitter,
StructuredHTTPEmitter,
CloudEvent
} = require("../v1/index.js");
SPEC_V1
} = require("../lib/bindings/http/constants.js");
const { Spec } = require("../v1/index.js");
const CloudEvent = require("../lib/cloudevent.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary.js");
const StructuredHTTPEmitter = require("../lib/bindings/http/emitter_structured.js");
const type = "com.github.pull.create";
const source = "urn:event:from:myapi/resource/123";
const contentType = "application/cloudevents+json; charset=utf-8";
@ -27,8 +27,7 @@ const ext1Value = "foobar";
const ext2Name = "extension2";
const ext2Value = "acme";
const cloudevent =
new CloudEvent(Spec)
const cloudevent = new CloudEvent(Spec)
.type(type)
.source(source)
.dataContentType(ceContentType)
@ -47,8 +46,8 @@ const httpcfg = {
url: `${webhook}/json`
};
const binary = new BinaryHTTPEmitter(httpcfg);
const structured = new StructuredHTTPEmitter(httpcfg);
const binary = new BinaryHTTPEmitter(SPEC_V1);
const structured = new StructuredHTTPEmitter();
describe("HTTP Transport Binding - Version 1.0", () => {
beforeEach(() => {
@ -68,7 +67,7 @@ describe("HTTP Transport Binding - Version 1.0", () => {
key: "other value"
})
});
return event.emit(cloudevent).then((response) => {
return event.emit(httpcfg, cloudevent).then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(contentType);
});
@ -76,14 +75,14 @@ describe("HTTP Transport Binding - Version 1.0", () => {
describe("JSON Format", () => {
it(`requires '${contentType}' Content-Type in the header`,
() => structured.emit(cloudevent)
() => structured.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(contentType);
}));
it("the request payload should be correct",
() => structured.emit(cloudevent)
() => structured.emit(httpcfg, cloudevent)
.then((response) => {
expect(JSON.parse(response.config.data))
.to.deep.equal(cloudevent.format());
@ -102,7 +101,7 @@ describe("HTTP Transport Binding - Version 1.0", () => {
.addExtension(ext1Name, ext1Value)
.addExtension(ext2Name, ext2Value);
return structured.emit(binevent)
return structured.emit(httpcfg, binevent)
.then((response) => {
expect(JSON.parse(response.config.data).data_base64)
.to.equal(expected);
@ -119,7 +118,7 @@ describe("HTTP Transport Binding - Version 1.0", () => {
.addExtension(ext1Name, ext1Value)
.addExtension(ext2Name, ext2Value);
return structured.emit(binevent)
return structured.emit(httpcfg, binevent)
.then((response) => {
expect(JSON.parse(response.config.data))
.to.have.property("data_base64");
@ -130,30 +129,29 @@ describe("HTTP Transport Binding - Version 1.0", () => {
});
describe("Binary", () => {
it("works with mTLS authentication", () => {
const event = new BinaryHTTPEmitter({
it("works with mTLS authentication", () =>
binary.emit({
method: "POST",
url: `${webhook}/json`,
httpsAgent: new https.Agent({
cert: "some value",
key: "other value"
})
});
return event.emit(cloudevent).then((response) => {
}, cloudevent).then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(cloudevent.getDataContentType());
});
});
})
);
describe("JSON Format", () => {
it(`requires '${cloudevent.getDataContentType()}' in the header`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers["Content-Type"])
.to.equal(cloudevent.getDataContentType());
}));
it("the request payload should be correct", () => binary.emit(cloudevent)
it("the request payload should be correct", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(JSON.parse(response.config.data))
.to.deep.equal(cloudevent.getData());
@ -172,122 +170,122 @@ describe("HTTP Transport Binding - Version 1.0", () => {
.addExtension(ext1Name, ext1Value)
.addExtension(ext2Name, ext2Value);
return binary.emit(binevent)
return binary.emit(httpcfg, binevent)
.then((response) => {
expect(response.config.data)
.to.equal(expected);
});
});
it("HTTP Header contains 'ce-type'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-type'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-type");
}));
it("HTTP Header contains 'ce-specversion'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-specversion'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-specversion");
}));
it("HTTP Header contains 'ce-source'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-source'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-source");
}));
it("HTTP Header contains 'ce-id'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-id'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-id");
}));
it("HTTP Header contains 'ce-time'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-time'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-time");
}));
it("HTTP Header contains 'ce-dataschema'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-dataschema'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-dataschema");
}));
it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(cloudevent)
it(`HTTP Header contains 'ce-${ext1Name}'`, () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property(`ce-${ext1Name}`);
}));
it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(cloudevent)
it(`HTTP Header contains 'ce-${ext2Name}'`, () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property(`ce-${ext2Name}`);
}));
it("HTTP Header contains 'ce-subject'", () => binary.emit(cloudevent)
it("HTTP Header contains 'ce-subject'", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(response.config.headers)
.to.have.property("ce-subject");
}));
it("should 'ce-type' have the right value", () => binary.emit(cloudevent)
it("should 'ce-type' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getType())
.to.equal(response.config.headers["ce-type"]);
}));
it("should 'ce-specversion' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSpecversion())
.to.equal(response.config.headers["ce-specversion"]);
}));
it("should 'ce-source' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSource())
.to.equal(response.config.headers["ce-source"]);
}));
it("should 'ce-id' have the right value", () => binary.emit(cloudevent)
it("should 'ce-id' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getId())
.to.equal(response.config.headers["ce-id"]);
}));
it("should 'ce-time' have the right value", () => binary.emit(cloudevent)
it("should 'ce-time' have the right value", () => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getTime())
.to.equal(response.config.headers["ce-time"]);
}));
it("should 'ce-dataschema' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getDataschema())
.to.equal(response.config.headers["ce-dataschema"]);
}));
it(`should 'ce-${ext1Name}' have the right value`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getExtensions()[ext1Name])
.to.equal(response.config.headers[`ce-${ext1Name}`]);
}));
it(`should 'ce-${ext2Name}' have the right value`,
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getExtensions()[ext2Name])
.to.equal(response.config.headers[`ce-${ext2Name}`]);
}));
it("should 'ce-subject' have the right value",
() => binary.emit(cloudevent)
() => binary.emit(httpcfg, cloudevent)
.then((response) => {
expect(cloudevent.getSubject())
.to.equal(response.config.headers["ce-subject"]);

View File

@ -1,69 +1,39 @@
const expect = require("chai").expect;
const v03 = require("../v03/index.js");
const v1 = require("../v1/index.js");
const { CloudEvent, HTTPReceiver, HTTPEmitter } = require("../");
const SpecV03 = require("../v03").Spec;
const SpecV1 = require("../v1").Spec;
const {
SPEC_V03,
SPEC_V1
} = require("../lib/bindings/http/constants.js");
describe("The SDK Requirements", () => {
it("should expose a CloudEvent type", () => {
const event = new CloudEvent();
expect(event instanceof CloudEvent).to.equal(true);
});
it("should expose an HTTPReceiver type", () => {
const receiver = new HTTPReceiver();
expect(receiver instanceof HTTPReceiver).to.equal(true);
});
it("should expose an HTTPEmitter type", () => {
const emitter = new HTTPEmitter({
url: "http://example.com"
});
expect(emitter instanceof HTTPEmitter).to.equal(true);
});
describe("v0.3", () => {
it("should create an event using the right spec version", () => {
expect(v03.event().spec.payload.specversion).to.equal("0.3");
});
it("should exports 'Spec'", () => {
expect(v03).to.have.property("Spec");
});
it("should exports 'StructuredHTTPEmitter'", () => {
expect(v03).to.have.property("StructuredHTTPEmitter");
});
it("should exports 'StructuredHTTPReceiver'", () => {
expect(v03).to.have.property("StructuredHTTPReceiver");
});
it("should exports 'BinaryHTTPEmitter'", () => {
expect(v03).to.have.property("BinaryHTTPEmitter");
});
it("should exports 'BinaryHTTPReceiver'", () => {
expect(v03).to.have.property("BinaryHTTPReceiver");
});
it("should exports 'HTTPUnmarshaller'", () => {
expect(v03).to.have.property("HTTPUnmarshaller");
});
it("should exports 'event'", () => {
expect(v03).to.have.property("event");
expect(new CloudEvent(SpecV03).spec.payload.specversion).to.equal(SPEC_V03);
});
});
describe("v1.0", () => {
it("should create an event using the right spec version", () => {
expect(v1.event().spec.payload.specversion).to.equal("1.0");
});
it("should exports 'Spec'", () => {
expect(v1).to.have.property("Spec");
});
it("should exports 'StructuredHTTPEmitter'", () => {
expect(v1).to.have.property("StructuredHTTPEmitter");
});
it("should exports 'StructuredHTTPReceiver'", () => {
expect(v1).to.have.property("StructuredHTTPReceiver");
});
it("should exports 'BinaryHTTPEmitter'", () => {
expect(v1).to.have.property("BinaryHTTPEmitter");
});
it("should exports 'BinaryHTTPReceiver'", () => {
expect(v1).to.have.property("BinaryHTTPReceiver");
});
it("should exports 'event'", () => {
expect(v1).to.have.property("event");
expect(new CloudEvent(SpecV1).spec.payload.specversion).to.equal(SPEC_V1);
});
});
});

View File

@ -1,28 +1,5 @@
const CloudEvent = require("../lib/cloudevent.js");
const Spec = require("../lib/specs/spec_0_3.js");
const StructuredHTTPEmitter =
require("../lib/bindings/http/emitter_structured.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary_0_3.js");
const StructuredHTTPReceiver =
require("../lib/bindings/http/receiver_structured_0_3.js");
const BinaryHTTPReceiver =
require("../lib/bindings/http/receiver_binary_0_3.js");
const HTTPUnmarshaller = require("../lib/bindings/http/unmarshaller_0_3.js");
function newEvent() {
return new CloudEvent(Spec);
}
module.exports = {
Spec,
StructuredHTTPEmitter,
StructuredHTTPReceiver,
BinaryHTTPEmitter,
BinaryHTTPReceiver,
HTTPUnmarshaller,
CloudEvent: newEvent,
event: newEvent
Spec
};

View File

@ -1,27 +1,5 @@
const CloudEvent = require("../lib/cloudevent.js");
const Spec = require("../lib/specs/spec_1.js");
const StructuredHTTPEmitter =
require("../lib/bindings/http/emitter_structured.js");
const BinaryHTTPEmitter = require("../lib/bindings/http/emitter_binary_1.js");
const StructuredHTTPReceiver =
require("../lib/bindings/http/receiver_structured_1.js");
const BinaryHTTPReceiver =
require("../lib/bindings/http/receiver_binary_1.js");
function newEvent() {
return new CloudEvent(Spec);
}
module.exports = {
Spec,
StructuredHTTPEmitter,
BinaryHTTPEmitter,
StructuredHTTPReceiver,
BinaryHTTPReceiver,
CloudEvent: newEvent,
event: newEvent
Spec
};