From 6f0b5ea5f11ae8a451df2c46208bbd1e08ff7227 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Mon, 18 May 2020 11:34:22 -0400 Subject: [PATCH] lib!: refactor HTTP bindings and specifications (#165) This is a breaking change. This commit makes a number of changes to the HTTP bindings code in an attempt to simplify its usage and implementation. From a very high level, this inverts the existing dependencies. As an example, consider `lib/bindings/http/receiver_structured_1.js`. https://github.com/cloudevents/sdk-javascript/blob/v1.0.0/lib/bindings/http/receiver_structured_0_3.js This class instantiates `lib/bindings/http/receiver_structured.js` and delegates its function invokations to it. This had the effect of requiring a user to know what event versions they would be receiving. And for me personally was a little confusing as a maintainer. The change introduced here reverses that logic, so that the version agnostic receiver is what the user instantiates. It instantiates the approrpiate version of a specific receiever and delegates to it - reversing the dependencies. I've also moved all of the top level directories related to HTTP versions into `lib/bindings/http/v1` and `lib/bindings/http/v03` and generally done some rearranging to make the repository structure cleaner and more organized. Signed-off-by: Lance Ball --- docs/BinaryHTTPEmitter.html | 535 ++++++++++ docs/BinaryHTTPReceiver.html | 715 ++++++++++++++ docs/CloudEvent.html | 12 +- docs/HTTPEmitter.html | 918 ++++++++++++++++++ docs/HTTPReceiver.html | 6 +- docs/StructuredHTTPEmitter.html | 472 +++++++++ docs/ValidationError.html | 352 +++++++ docs/bindings_http_emitter_binary.js.html | 201 ++++ docs/bindings_http_emitter_structured.js.html | 152 +++ docs/bindings_http_http_emitter.js.html | 200 ++++ docs/bindings_http_http_receiver.js.html | 47 +- docs/bindings_http_receiver_binary.js.html | 168 ++++ ...s_http_validation_validation_error.js.html | 131 +++ docs/cloudevent.js.html | 18 +- docs/formats_json_parser.js.html | 11 +- docs/index.html | 62 +- docs/validation_error.js.html | 131 +++ lib/bindings/http/emitter_binary.js | 6 +- lib/bindings/http/http_receiver.js | 20 +- lib/bindings/http/receiver_binary.js | 172 +--- lib/bindings/http/receiver_binary_0_3.js | 119 --- lib/bindings/http/receiver_binary_1.js | 78 -- lib/bindings/http/receiver_structured.js | 97 +- lib/bindings/http/unmarshaller.js | 76 -- lib/bindings/http/unmarshaller_0_3.js | 19 - .../http/{ => v03}/emitter_binary_0_3.js | 2 +- lib/bindings/http/v03/index.js | 9 + lib/bindings/http/v03/receiver_binary_0_3.js | 96 ++ .../http/{ => v03}/receiver_structured_0_3.js | 47 +- lib/{specs => bindings/http/v03}/spec_0_3.js | 6 +- .../http/{ => v1}/emitter_binary_1.js | 2 +- lib/bindings/http/v1/index.js | 9 + lib/bindings/http/v1/receiver_binary_1.js | 55 ++ .../http/{ => v1}/receiver_structured_1.js | 47 +- lib/{specs => bindings/http/v1}/spec_1.js | 6 +- lib/bindings/http/validation/binary.js | 111 +++ lib/bindings/http/{ => validation}/commons.js | 22 +- .../http/validation}/fun.js | 0 lib/bindings/http/validation/structured.js | 57 ++ .../http/validation}/validation_error.js | 2 +- lib/cloudevent.js | 2 +- lib/formats/json/parser.js | 4 +- package.json | 4 +- test/bindings/http/http_emitter_test.js | 4 +- .../http/promiscuous_receiver_test.js | 2 +- .../http/receiver_binary_0_3_tests.js | 6 +- test/bindings/http/receiver_binary_1_tests.js | 9 +- .../http/receiver_structured_0_3_test.js | 11 +- .../http/receiver_structured_1_test.js | 13 +- test/bindings/http/unmarshaller_0_3_tests.js | 215 ---- test/formats/json/parser_test.js | 2 +- test/fun_tests.js | 2 +- test/http_binding_0_3.js | 2 +- test/http_binding_1.js | 4 +- test/sdk_test.js | 4 +- test/spec_0_3_tests.js | 4 +- test/spec_1_tests.js | 6 +- v03/index.js | 5 - v1/index.js | 5 - 59 files changed, 4593 insertions(+), 900 deletions(-) create mode 100644 docs/BinaryHTTPEmitter.html create mode 100644 docs/BinaryHTTPReceiver.html create mode 100644 docs/HTTPEmitter.html create mode 100644 docs/StructuredHTTPEmitter.html create mode 100644 docs/ValidationError.html create mode 100644 docs/bindings_http_emitter_binary.js.html create mode 100644 docs/bindings_http_emitter_structured.js.html create mode 100644 docs/bindings_http_http_emitter.js.html create mode 100644 docs/bindings_http_receiver_binary.js.html create mode 100644 docs/bindings_http_validation_validation_error.js.html create mode 100644 docs/validation_error.js.html delete mode 100644 lib/bindings/http/receiver_binary_0_3.js delete mode 100644 lib/bindings/http/receiver_binary_1.js delete mode 100644 lib/bindings/http/unmarshaller.js delete mode 100644 lib/bindings/http/unmarshaller_0_3.js rename lib/bindings/http/{ => v03}/emitter_binary_0_3.js (96%) create mode 100644 lib/bindings/http/v03/index.js create mode 100644 lib/bindings/http/v03/receiver_binary_0_3.js rename lib/bindings/http/{ => v03}/receiver_structured_0_3.js (53%) rename lib/{specs => bindings/http/v03}/spec_0_3.js (97%) rename lib/bindings/http/{ => v1}/emitter_binary_1.js (96%) create mode 100644 lib/bindings/http/v1/index.js create mode 100644 lib/bindings/http/v1/receiver_binary_1.js rename lib/bindings/http/{ => v1}/receiver_structured_1.js (52%) rename lib/{specs => bindings/http/v1}/spec_1.js (97%) create mode 100644 lib/bindings/http/validation/binary.js rename lib/bindings/http/{ => validation}/commons.js (52%) rename lib/{utils => bindings/http/validation}/fun.js (100%) create mode 100644 lib/bindings/http/validation/structured.js rename lib/{ => bindings/http/validation}/validation_error.js (88%) delete mode 100644 test/bindings/http/unmarshaller_0_3_tests.js delete mode 100644 v03/index.js delete mode 100644 v1/index.js diff --git a/docs/BinaryHTTPEmitter.html b/docs/BinaryHTTPEmitter.html new file mode 100644 index 00000000..a32a899b --- /dev/null +++ b/docs/BinaryHTTPEmitter.html @@ -0,0 +1,535 @@ + + + + + + + + + BinaryHTTPEmitter - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ BinaryHTTPEmitter +

+ + + + +
+
+ +

+ + BinaryHTTPEmitter + +

+ + +
+

A class to emit binary CloudEvents over HTTP.

+
+ + +
+ +
+
+ + + + + +

Constructor

+ + + + + + + + +

+ new BinaryHTTPEmitter(version) +

+
+ + + + + +
+

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

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
version + + + + string + + + + + + + +

the CloudEvent HTTP specification version. +Default: 1.0

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Methods

+ + + + + + + + + + + + + +

+ (async) emit(options, cloudevent) → {Promise} +

+
+ + + + + +
+

Sends this cloud event to a receiver over HTTP.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + + + Object + + + + + + + +

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

+ +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
url + + + + URL + + + + + + + +

The HTTP/S url that should receive this event

+ +
+ + +
cloudevent + + + + Object + + + + + + + +

the CloudEvent to be sent

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/BinaryHTTPReceiver.html b/docs/BinaryHTTPReceiver.html new file mode 100644 index 00000000..2e185d25 --- /dev/null +++ b/docs/BinaryHTTPReceiver.html @@ -0,0 +1,715 @@ + + + + + + + + + BinaryHTTPReceiver - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ BinaryHTTPReceiver +

+ + + + +
+
+ +

+ + BinaryHTTPReceiver + +

+ + +
+

A class that receives binary CloudEvents over HTTP. This class can be used +if you know that all incoming events will be using binary transport. If +events can come as either binary or structured, use {HTTPReceiver}.

+
+ + +
+ +
+
+ + + + + +

Constructor

+ + + + + + + + +

+ new BinaryHTTPReceiver(version) +

+
+ + + + + +
+

Creates a new BinaryHTTPReceiver to accept events over HTTP.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
version + + + + string + + + + + + + +

the Cloud Event specification version to use. Default "1.0"

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Methods

+ + + + + + + + + + + + + +

+ check(payload, headers) → {void} +

+
+ + + + + +
+

Checks an incoming HTTP request to determine if it conforms to the +Cloud Event specification for this receiver.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
payload + + + + Object + + + + + + + +

the HTTP request body

+ +
headers + + + + Object + + + + + + + +

the HTTP request headers

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+

if the event does not conform to the spec

+
+
+
+
+
+
Type
+
+ + + ValidationError + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +

+ parse(payload, headers) → {CloudEvent} +

+
+ + + + + +
+

Parses an incoming HTTP request, converting it to a {CloudEvent} +instance if it conforms to the Cloud Event specification for this receiver.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
payload + + + + Object + + + + + + + +

the HTTP request body

+ +
headers + + + + Object + + + + + + + +

the HTTP request headers

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+

of the event does not conform to the spec

+
+
+
+
+
+
Type
+
+ + + ValidationError + + + + + +
+
+
+
+
+ + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/CloudEvent.html b/docs/CloudEvent.html index 3d08a136..a661f10f 100644 --- a/docs/CloudEvent.html +++ b/docs/CloudEvent.html @@ -73,7 +73,7 @@

-

Classes

+

Classes

@@ -119,7 +119,7 @@

Constructor

- new CloudEvent(UserSpecopt, UserFormatteropt) + new CloudEvent(userSpecopt, userFormatteropt)

@@ -163,7 +163,7 @@
Parameters:
- UserSpec + userSpec @@ -202,7 +202,7 @@
Parameters:
- UserFormatter + userFormatter @@ -397,7 +397,7 @@
Parameters:
-

the name of the exteneion attribute

+

the name of the extension attribute

@@ -843,7 +843,7 @@

-

Format the CloudEvent as JSON. Validates the event according +

Formats the CloudEvent as JSON. Validates the event according to the CloudEvent specification and throws an exception if it's invalid.

diff --git a/docs/HTTPEmitter.html b/docs/HTTPEmitter.html new file mode 100644 index 00000000..0e10683f --- /dev/null +++ b/docs/HTTPEmitter.html @@ -0,0 +1,918 @@ + + + + + + + + + HTTPEmitter - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ HTTPEmitter +

+ + + + +
+
+ +

+ + HTTPEmitter + +

+ + +
+

A class which is capable of sending binary and structured events using +the CloudEvents HTTP Protocol Binding specification.

+
+ + +
+ +
+
+ + + + + +

Constructor

+ + + + + + + + +

+ new HTTPEmitter(optionsopt) +

+
+ + + + + +
+

Creates a new instance of {HTTPEmitter}. The default emitter uses the 1.0 +protocol specification in binary mode.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
options + + + + Object + + + + + + + + + <optional>
+ + + + + +
+

The configuration options for this event emitter

+ +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
url + + + + URL + + + + + + + + + + + + + +

The endpoint that will receive the sent events.

+ +
version + + + + string + + + + + + + + + <optional>
+ + + + + +
+

The HTTP binding specification version. Default: "1.0"

+ +
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + +
See:
+
+ +
+ + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+

if no options.url is provided or an unknown specification version is provided.

+
+
+
+
+
+
Type
+
+ + + TypeError + + + + + +
+
+
+
+
+ + + + + + + +
+ + + + + + + + + + + + + + +

Methods

+ + + + + + + + + + + + + +

+ headers(event) → {Object} +

+
+ + + + + +
+

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.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
event + + + + CloudEvent + + + + + + + +

a CloudEvent

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ send(event, optionsopt) → {Promise} +

+
+ + + + + +
+

Sends the {CloudEvent} to an event receiver over HTTP POST

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
event + + + + CloudEvent + + + + + + + + + + + + + +

the CloudEvent to be sent

+ +
options + + + + Object + + + + + + + + + <optional>
+ + + + + +
+

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

+ +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
url + + + + URL + + + + + + + + + <optional>
+ + + + + +
+

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.

+ +
mode + + + + string + + + + + + + + + <optional>
+ + + + + +
+

the message mode for sending this event. +Possible values are "binary" and "structured". Default: structured

+ +
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/HTTPReceiver.html b/docs/HTTPReceiver.html index d34ea689..82636f2a 100644 --- a/docs/HTTPReceiver.html +++ b/docs/HTTPReceiver.html @@ -73,7 +73,7 @@

-

Classes

+

Classes

@@ -171,7 +171,7 @@

@@ -371,7 +371,7 @@

Parameters:
diff --git a/docs/StructuredHTTPEmitter.html b/docs/StructuredHTTPEmitter.html new file mode 100644 index 00000000..e8e8dde7 --- /dev/null +++ b/docs/StructuredHTTPEmitter.html @@ -0,0 +1,472 @@ + + + + + + + + + StructuredHTTPEmitter - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ StructuredHTTPEmitter +

+ + + + +
+
+ +

+ + StructuredHTTPEmitter + +

+ + +
+

A class for sending {CloudEvent} instances over HTTP.

+
+ + +
+ +
+
+ + + + + +

Constructor

+ + + + + + + + +

+ new StructuredHTTPEmitter() +

+
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Methods

+ + + + + + + + + + + + + +

+ (async) emit(options, cloudevent) → {Promise} +

+
+ + + + + +
+

Sends the event over HTTP

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + + + Object + + + + + + + +

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

+ +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
url + + + + URL + + + + + + + +

The HTTP/S url that should receive this event

+ +
+ + +
cloudevent + + + + CloudEvent + + + + + + + +

The CloudEvent to be sent

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/ValidationError.html b/docs/ValidationError.html new file mode 100644 index 00000000..827aaa4b --- /dev/null +++ b/docs/ValidationError.html @@ -0,0 +1,352 @@ + + + + + + + + + ValidationError - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ ValidationError +

+ + + + +
+
+ +

+ + ValidationError + +

+ + +
+

A Error class that will be thrown when a CloudEvent +cannot be properly validated against a specification.

+
+ + +
+ +
+
+ + + + + +

Constructor

+ + + + + + + + +

+ new ValidationError(message, errorsopt) +

+
+ + + + + +
+

Constructs a new {ValidationError} with the message +and array of additional errors.

+
+ + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
message + + + + string + + + + + + + + + + + + + +

the error message

+ +
errors + + + + Array.<string> + + + | + + + Array.<ErrorObject> + + + + + + + + + <optional>
+ + + + + +
+

any additional errors related to validation

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/bindings_http_emitter_binary.js.html b/docs/bindings_http_emitter_binary.js.html new file mode 100644 index 00000000..b06d979c --- /dev/null +++ b/docs/bindings_http_emitter_binary.js.html @@ -0,0 +1,201 @@ + + + + + + + + + + + bindings/http/emitter_binary.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ bindings/http/emitter_binary.js +

+ + + + + +
+
+
const axios = require("axios");
+const EmitterV1 = require("./v1").BinaryEmitter;
+const EmitterV3 = require("./v03").BinaryEmitter;
+
+const {
+  HEADERS,
+  BINARY_HEADERS_03,
+  BINARY_HEADERS_1,
+  HEADER_CONTENT_TYPE,
+  DEFAULT_CONTENT_TYPE,
+  DATA_ATTRIBUTE,
+  SPEC_V1,
+  SPEC_V03
+} = require("./constants.js");
+
+const defaults = {
+  [HEADERS]: {
+    [HEADER_CONTENT_TYPE]: DEFAULT_CONTENT_TYPE
+  },
+  method: "POST"
+};
+
+/**
+ * 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 = EmitterV1;
+      this.extensionPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX;
+    } else if (version === SPEC_V03) {
+      this.headerByGetter = EmitterV3;
+      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]());
+      });
+
+    // Set the cloudevent payload
+    const formatted = cloudevent.format();
+    let data = formatted.data;
+    data = (formatted.data_base64 ? formatted.data_base64 : data);
+
+    // Have extensions?
+    const exts = cloudevent.getExtensions();
+    Object.keys(exts)
+      .filter((ext) => Object.hasOwnProperty.call(exts, ext))
+      .forEach((ext) => {
+        headers[this.extensionPrefix + ext] = exts[ext];
+      });
+
+    config[DATA_ATTRIBUTE] = data;
+    config.headers = headers;
+
+    // Return the Promise
+    return axios.request(config);
+  }
+}
+
+module.exports = BinaryHTTPEmitter;
+
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/docs/bindings_http_emitter_structured.js.html b/docs/bindings_http_emitter_structured.js.html new file mode 100644 index 00000000..0e6144c8 --- /dev/null +++ b/docs/bindings_http_emitter_structured.js.html @@ -0,0 +1,152 @@ + + + + + + + + + + + bindings/http/emitter_structured.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ bindings/http/emitter_structured.js +

+ + + + + +
+
+
const axios = require("axios");
+const {
+  DATA_ATTRIBUTE,
+  DEFAULT_CE_CONTENT_TYPE,
+  HEADERS,
+  HEADER_CONTENT_TYPE
+} = require("./constants.js");
+
+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;
+
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/docs/bindings_http_http_emitter.js.html b/docs/bindings_http_http_emitter.js.html new file mode 100644 index 00000000..7947483d --- /dev/null +++ b/docs/bindings_http_http_emitter.js.html @@ -0,0 +1,200 @@ + + + + + + + + + + + bindings/http/http_emitter.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ bindings/http/http_emitter.js +

+ + + + + +
+
+
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;
+
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/docs/bindings_http_http_receiver.js.html b/docs/bindings_http_http_receiver.js.html index 9966ac64..35c97036 100644 --- a/docs/bindings_http_http_receiver.js.html +++ b/docs/bindings_http_http_receiver.js.html @@ -75,7 +75,7 @@

-

Classes

+

Classes

@@ -90,11 +90,16 @@

-
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 V03Binary = require("./receiver_binary_0_3.js");
+// const V03Structured = require("./v03/receiver_structured_0_3.js");
+// const V1Binary = require("./receiver_binary_1.js");
+// const V1Structured = require("./v1/receiver_structured_1.js");
+const BinaryReceiver = require("./receiver_binary.js");
+const StructuredReceiver = require("./receiver_structured.js");
+const ValidationError = require("./validation/validation_error.js");
 const {
+  BINARY,
+  STRUCTURED,
   SPEC_V03,
   SPEC_V1,
   HEADER_CONTENT_TYPE,
@@ -113,12 +118,12 @@ 

constructor() { this.receivers = { v1: { - structured: new V1Structured(), - binary: new V1Binary() + structured: new StructuredReceiver(SPEC_V1), + binary: new BinaryReceiver(SPEC_V1) }, v03: { - structured: new V03Structured(), - binary: new V03Binary() + structured: new StructuredReceiver(SPEC_V03), + binary: new BinaryReceiver(SPEC_V03) } }; } @@ -148,31 +153,29 @@

} function getMode(headers) { - let mode = "unknown"; const contentType = headers[HEADER_CONTENT_TYPE]; if (contentType && contentType.startsWith(MIME_CE)) { - mode = "structured"; - } else if (headers[BINARY_HEADERS_1.ID]) { - mode = "binary"; - } else { - throw new TypeError("no cloud event detected"); + return STRUCTURED; + } + if (headers[BINARY_HEADERS_1.ID]) { + return BINARY; } - return mode; + throw new ValidationError("no cloud event detected"); } function getVersion(mode, headers, body) { - let version = SPEC_V1; // default to 1.0 - - if (mode === "binary") { + if (mode === BINARY) { // Check the headers for the version const versionHeader = headers[DEFAULT_SPEC_VERSION_HEADER]; - if (versionHeader) { version = versionHeader; } + if (versionHeader) { + return versionHeader; + } } else { // structured mode - the version is in the body - version = body instanceof String + return body instanceof String ? JSON.parse(body).specversion : body.specversion; } - return version; + return SPEC_V1; } module.exports = HTTPReceiver; diff --git a/docs/bindings_http_receiver_binary.js.html b/docs/bindings_http_receiver_binary.js.html new file mode 100644 index 00000000..2a8814d3 --- /dev/null +++ b/docs/bindings_http_receiver_binary.js.html @@ -0,0 +1,168 @@ + + + + + + + + + + + bindings/http/receiver_binary.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ bindings/http/receiver_binary.js +

+ + + + + +
+
+
const ReceiverV1 = require("./v1/receiver_binary_1.js");
+const ReceiverV3 = require("./v03/receiver_binary_0_3.js");
+
+const { SPEC_V03, SPEC_V1 } = require("./constants.js");
+const { check, parse } = require("./validation/binary.js");
+
+/**
+ * A class that receives binary CloudEvents over HTTP. This class can be used
+ * if you know that all incoming events will be using binary transport. If
+ * events can come as either binary or structured, use {HTTPReceiver}.
+ */
+class BinaryHTTPReceiver {
+  /**
+   * Creates a new BinaryHTTPReceiver to accept events over HTTP.
+   *
+   * @param {string} version the Cloud Event specification version to use. Default "1.0"
+   */
+  constructor(version = SPEC_V1) {
+    if (version === SPEC_V1) {
+      this.receiver = new ReceiverV1();
+    } else if (version === SPEC_V03) {
+      this.receiver = new ReceiverV3();
+    }
+  }
+
+  /**
+   * Checks an incoming HTTP request to determine if it conforms to the
+   * Cloud Event specification for this receiver.
+   *
+   * @throws {ValidationError} if the event does not conform to the spec
+   * @param {Object} payload the HTTP request body
+   * @param {Object} headers  the HTTP request headers
+   *
+   * @returns {void}
+   */
+  check(payload, headers) {
+    return check(payload, headers, this.receiver);
+  }
+
+  /**
+   * Parses an incoming HTTP request, converting it to a {CloudEvent}
+   * instance if it conforms to the Cloud Event specification for this receiver.
+   *
+   * @throws {ValidationError} of the event does not conform to the spec
+   * @param {Object} payload the HTTP request body
+   * @param {Object} headers the HTTP request headers
+   * @returns {CloudEvent} an instance of CloudEvent representing the incoming request
+   */
+  parse(payload, headers) {
+    return parse(payload, headers, this.receiver);
+  }
+}
+
+module.exports = BinaryHTTPReceiver;
+
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/docs/bindings_http_validation_validation_error.js.html b/docs/bindings_http_validation_validation_error.js.html new file mode 100644 index 00000000..b6b71669 --- /dev/null +++ b/docs/bindings_http_validation_validation_error.js.html @@ -0,0 +1,131 @@ + + + + + + + + + + + bindings/http/validation/validation_error.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ bindings/http/validation/validation_error.js +

+ + + + + +
+
+
/**
+ * A Error class that will be thrown when a CloudEvent
+ * cannot be properly validated against a specification.
+ */
+class ValidationError extends TypeError {
+  /**
+   * Constructs a new {ValidationError} with the message
+   * and array of additional errors.
+   * @param {string} message the error message
+   * @param {string[]|ErrorObject[]} [errors] any additional errors related to validation
+   */
+  constructor(message, errors) {
+    super(message);
+    this.errors = errors ? errors : [];
+  }
+}
+
+module.exports = ValidationError;
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/docs/cloudevent.js.html b/docs/cloudevent.js.html index d9412f55..03399742 100644 --- a/docs/cloudevent.js.html +++ b/docs/cloudevent.js.html @@ -75,7 +75,7 @@

-

Classes

+

Classes

@@ -90,7 +90,7 @@

-
const Spec = require("./specs/spec_1.js");
+    
const Spec = require("./bindings/http/v1/spec_1.js");
 const Formatter = require("./formats/json/formatter.js");
 
 /**
@@ -99,12 +99,12 @@ 

class CloudEvent { /** * Creates a new CloudEvent instance - * @param {Spec} [UserSpec] A CloudEvent version specification - * @param {Formatter} [UserFormatter] Converts the event into a readable string + * @param {Spec} [userSpec] A CloudEvent version specification + * @param {Formatter} [userFormatter] Converts the event into a readable string */ - constructor(UserSpec, UserFormatter) { - this.spec = (UserSpec) ? new UserSpec(CloudEvent) : new Spec(CloudEvent); - this.formatter = (UserFormatter) ? new UserFormatter() : new Formatter(); + constructor(userSpec, userFormatter) { + this.spec = userSpec ? new userSpec(CloudEvent) : new Spec(CloudEvent); + this.formatter = userFormatter ? new userFormatter() : new Formatter(); // The map of extensions this.extensions = {}; @@ -119,7 +119,7 @@

} /** - * Format the CloudEvent as JSON. Validates the event according + * Formats the CloudEvent as JSON. Validates the event according * to the CloudEvent specification and throws an exception if * it's invalid. * @returns {JSON} the CloudEvent in JSON form @@ -299,7 +299,7 @@

/** * Adds an extension attribute to this CloudEvent * @see https://github.com/cloudevents/spec/blob/master/spec.md#extension-context-attributes - * @param {*} key the name of the exteneion attribute + * @param {*} key the name of the extension attribute * @param {*} value the value of the extension attribute * @returns {CloudEvent} this CloudEvent instance */ diff --git a/docs/formats_json_parser.js.html b/docs/formats_json_parser.js.html index 3121598a..4783986a 100644 --- a/docs/formats_json_parser.js.html +++ b/docs/formats_json_parser.js.html @@ -75,7 +75,7 @@

-

Classes

+

Classes

@@ -94,12 +94,11 @@

isString, isDefinedOrThrow, isStringOrObjectOrThrow -} = require("../../utils/fun.js"); +} = require("../../bindings/http/validation/fun.js"); +const ValidationError = require("../../bindings/http/validation/validation_error.js"); -const invalidPayloadTypeError = - new Error("invalid payload type, allowed are: string or object"); -const nullOrUndefinedPayload = - new Error("null or undefined payload"); +const invalidPayloadTypeError = new ValidationError("invalid payload type, allowed are: string or object"); +const nullOrUndefinedPayload = new ValidationError("null or undefined payload"); const asJSON = (v) => (isString(v) ? JSON.parse(v) : v); diff --git a/docs/index.html b/docs/index.html index f8b5e850..3dc53b0f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -73,7 +73,7 @@

-

Classes

+

Classes

@@ -136,23 +136,57 @@

Receiving Events

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.

-
const { CloudEvent } = require("cloudevents-sdk");
-const { StructuredHTTPEmitter } = require("cloudevents-sdk/v1");
-
-const myevent = new CloudEvent()
-  .type("com.github.pull.create")
-  .source("urn:event:from:myapi/resource/123");
-
-const emitter = new StructuredHTTPEmitter({
-  method: "POST",
-  url   : "https://myserver.com"
+

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().

+
const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk");
+
+// 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)
+
+// 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"
+});
+
+// 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);
 
-// Emit the event
-emitter.emit(myevent)
 

Supported specification features

diff --git a/docs/validation_error.js.html b/docs/validation_error.js.html new file mode 100644 index 00000000..332a5154 --- /dev/null +++ b/docs/validation_error.js.html @@ -0,0 +1,131 @@ + + + + + + + + + + + validation_error.js - Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ validation_error.js +

+ + + + + +
+
+
/**
+ * A Error class that will be thrown when a CloudEvent
+ * cannot be properly validated against a the specification.
+ */
+class ValidationError extends TypeError {
+  /**
+   * Constructs a new {ValidationError} with the message
+   * and array of additional errors.
+   * @param {string} message the error message
+   * @param {string[]|ErrorObject[]} [errors] any additional errors related to validation
+   */
+  constructor(message, errors) {
+    super(message);
+    this.errors = errors ? errors : [];
+  }
+}
+
+module.exports = ValidationError;
+
+
+ + + + +
+ +
+ + + + + + + + + + diff --git a/lib/bindings/http/emitter_binary.js b/lib/bindings/http/emitter_binary.js index 5bed37d9..ccd93330 100644 --- a/lib/bindings/http/emitter_binary.js +++ b/lib/bindings/http/emitter_binary.js @@ -1,4 +1,6 @@ const axios = require("axios"); +const EmitterV1 = require("./v1").BinaryEmitter; +const EmitterV3 = require("./v03").BinaryEmitter; const { HEADERS, @@ -32,10 +34,10 @@ class BinaryHTTPEmitter { */ constructor(version) { if (version === SPEC_V1) { - this.headerByGetter = require("./emitter_binary_1.js"); + this.headerByGetter = EmitterV1; this.extensionPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX; } else if (version === SPEC_V03) { - this.headerByGetter = require("./emitter_binary_0_3.js"); + this.headerByGetter = EmitterV3; this.extensionPrefix = BINARY_HEADERS_03.EXTENSIONS_PREFIX; } } diff --git a/lib/bindings/http/http_receiver.js b/lib/bindings/http/http_receiver.js index 78f5631c..55537101 100644 --- a/lib/bindings/http/http_receiver.js +++ b/lib/bindings/http/http_receiver.js @@ -1,8 +1,10 @@ -const V03Binary = require("./receiver_binary_0_3.js"); -const V03Structured = require("./receiver_structured_0_3.js"); -const V1Binary = require("./receiver_binary_1.js"); -const V1Structured = require("./receiver_structured_1.js"); -const ValidationError = require("../../validation_error.js"); +// const V03Binary = require("./receiver_binary_0_3.js"); +// const V03Structured = require("./v03/receiver_structured_0_3.js"); +// const V1Binary = require("./receiver_binary_1.js"); +// const V1Structured = require("./v1/receiver_structured_1.js"); +const BinaryReceiver = require("./receiver_binary.js"); +const StructuredReceiver = require("./receiver_structured.js"); +const ValidationError = require("./validation/validation_error.js"); const { BINARY, STRUCTURED, @@ -24,12 +26,12 @@ class HTTPReceiver { constructor() { this.receivers = { v1: { - structured: new V1Structured(), - binary: new V1Binary() + structured: new StructuredReceiver(SPEC_V1), + binary: new BinaryReceiver(SPEC_V1) }, v03: { - structured: new V03Structured(), - binary: new V03Binary() + structured: new StructuredReceiver(SPEC_V03), + binary: new BinaryReceiver(SPEC_V03) } }; } diff --git a/lib/bindings/http/receiver_binary.js b/lib/bindings/http/receiver_binary.js index 82087a13..f37ccd5a 100644 --- a/lib/bindings/http/receiver_binary.js +++ b/lib/bindings/http/receiver_binary.js @@ -1,134 +1,54 @@ -const { HEADER_CONTENT_TYPE, MIME_JSON, DEFAULT_SPEC_VERSION_HEADER } = - require("./constants.js"); -const Commons = require("./commons.js"); -const CloudEvent = require("../../cloudevent.js"); -const ValidationError = require("../../validation_error.js"); - -const { - isDefinedOrThrow, - isStringOrObjectOrThrow -} = require("../../utils/fun.js"); - -function validateArgs(payload, attributes) { - Array.of(payload) - .filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined"))) - .filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or a string"))) - .shift(); - - Array.of(attributes) - .filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined"))) - .shift(); -} - -function BinaryHTTPReceiver( - parsersByEncoding, - setterByHeader, - allowedContentTypes, - requiredHeaders, - Spec, - specversion, - extensionsPrefix, - checkDecorator) { - this.parsersByEncoding = parsersByEncoding; - this.setterByHeader = setterByHeader; - this.allowedContentTypes = allowedContentTypes; - this.requiredHeaders = requiredHeaders; - this.Spec = Spec; - this.spec = new Spec(); - this.specversion = specversion; - this.extensionsPrefix = extensionsPrefix; - this.checkDecorator = checkDecorator; -} - -BinaryHTTPReceiver.prototype.check = function(payload, headers) { - // Validation Level 0 - validateArgs(payload, headers); - - if (this.checkDecorator) { - this.checkDecorator(payload, headers); +const ReceiverV1 = require("./v1/receiver_binary_1.js"); +const ReceiverV3 = require("./v03/receiver_binary_0_3.js"); + +const { SPEC_V03, SPEC_V1 } = require("./constants.js"); +const { check, parse } = require("./validation/binary.js"); + +/** + * A class that receives binary CloudEvents over HTTP. This class can be used + * if you know that all incoming events will be using binary transport. If + * events can come as either binary or structured, use {HTTPReceiver}. + */ +class BinaryHTTPReceiver { + /** + * Creates a new BinaryHTTPReceiver to accept events over HTTP. + * + * @param {string} version the Cloud Event specification version to use. Default "1.0" + */ + constructor(version = SPEC_V1) { + if (version === SPEC_V1) { + this.receiver = new ReceiverV1(); + } else if (version === SPEC_V03) { + this.receiver = new ReceiverV3(); + } } - // Clone and low case all headers names - const sanityHeaders = Commons.sanityAndClone(headers); - - // Validation Level 1 - if content-type exists, be sure it's - // an allowed type - const contentTypeHeader = sanityHeaders[HEADER_CONTENT_TYPE]; - const noContentType = !this.allowedContentTypes.includes(contentTypeHeader); - if (contentTypeHeader && noContentType) { - throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]); + /** + * Checks an incoming HTTP request to determine if it conforms to the + * Cloud Event specification for this receiver. + * + * @throws {ValidationError} if the event does not conform to the spec + * @param {Object} payload the HTTP request body + * @param {Object} headers the HTTP request headers + * + * @returns {void} + */ + check(payload, headers) { + return check(payload, headers, this.receiver); } - this.requiredHeaders - .filter((required) => !sanityHeaders[required]) - .forEach((required) => { - throw new ValidationError(`header '${required}' not found`); - }); - - if (sanityHeaders[DEFAULT_SPEC_VERSION_HEADER] !== - this.specversion) { - throw new ValidationError("invalid spec version", [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]]); + /** + * Parses an incoming HTTP request, converting it to a {CloudEvent} + * instance if it conforms to the Cloud Event specification for this receiver. + * + * @throws {ValidationError} of the event does not conform to the spec + * @param {Object} payload the HTTP request body + * @param {Object} headers the HTTP request headers + * @returns {CloudEvent} an instance of CloudEvent representing the incoming request + */ + parse(payload, headers) { + return parse(payload, headers, this.receiver); } - - // No erros! Its contains the minimum required attributes -}; - -function parserFor(parsersByEncoding, cloudevent, headers) { - const encoding = cloudevent.spec.payload.datacontentencoding; - return parsersByEncoding[encoding][headers[HEADER_CONTENT_TYPE]]; } -BinaryHTTPReceiver.prototype.parse = function(payload, headers) { - this.check(payload, headers); - - // Clone and low case all headers names - const sanityHeaders = Commons.sanityAndClone(headers); - if (!sanityHeaders[HEADER_CONTENT_TYPE]) { - sanityHeaders[HEADER_CONTENT_TYPE] = MIME_JSON; - } - - const processedHeaders = []; - const cloudevent = new CloudEvent(this.Spec); - - // dont worry, check() have seen what was required or not - Array.from(Object.keys(this.setterByHeader)) - .filter((header) => sanityHeaders[header]) - .forEach((header) => { - const setterName = this.setterByHeader[header].name; - const parserFun = this.setterByHeader[header].parser; - - // invoke the setter function - cloudevent[setterName](parserFun(sanityHeaders[header])); - - // to use ahead, for extensions processing - processedHeaders.push(header); - }); - - // Parses the payload - const parsedPayload = - parserFor(this.parsersByEncoding, cloudevent, sanityHeaders) - .parse(payload); - - // Every unprocessed header can be an extension - Array.from(Object.keys(sanityHeaders)) - .filter((value) => !processedHeaders.includes(value)) - .filter((value) => - value.startsWith(this.extensionsPrefix)) - .map((extension) => - extension.substring(this.extensionsPrefix.length) - ).forEach((extension) => - cloudevent.addExtension(extension, - sanityHeaders[this.extensionsPrefix + extension]) - ); - - // Sets the data - cloudevent.data(parsedPayload); - - // Checks the event spec - cloudevent.format(); - - // return the result - return cloudevent; -}; - module.exports = BinaryHTTPReceiver; diff --git a/lib/bindings/http/receiver_binary_0_3.js b/lib/bindings/http/receiver_binary_0_3.js deleted file mode 100644 index 943adb34..00000000 --- a/lib/bindings/http/receiver_binary_0_3.js +++ /dev/null @@ -1,119 +0,0 @@ -const Constants = require("./constants.js"); -const Spec = require("../../specs/spec_0_3.js"); -const ValidationError = require("../../validation_error.js"); - -const JSONParser = require("../../formats/json/parser.js"); -const Base64Parser = require("../../formats/base64.js"); - -const BinaryHTTPReceiver = require("./receiver_binary.js"); - -const parserByType = {}; -parserByType[Constants.MIME_JSON] = new JSONParser(); -parserByType[Constants.MIME_OCTET_STREAM] = { - parse(payload) { return payload; } -}; - -const parsersByEncoding = {}; -parsersByEncoding.null = parserByType; -parsersByEncoding[undefined] = parserByType; - -// base64 -parsersByEncoding[Constants.ENCODING_BASE64] = {}; -parsersByEncoding[Constants.ENCODING_BASE64][Constants.MIME_JSON] = - new JSONParser(new Base64Parser()); -parsersByEncoding[Constants.ENCODING_BASE64][Constants.MIME_OCTET_STREAM] = { - parse(payload) { return payload; } -}; - -const allowedContentTypes = []; -allowedContentTypes.push(Constants.MIME_JSON); -allowedContentTypes.push(Constants.MIME_OCTET_STREAM); - -const allowedEncodings = []; -allowedEncodings.push(Constants.ENCODING_BASE64); - -const requiredHeaders = []; -requiredHeaders.push(Constants.BINARY_HEADERS_03.TYPE); -requiredHeaders.push(Constants.BINARY_HEADERS_03.SPEC_VERSION); -requiredHeaders.push(Constants.BINARY_HEADERS_03.SOURCE); -requiredHeaders.push(Constants.BINARY_HEADERS_03.ID); - -const setterByHeader = {}; -setterByHeader[Constants.BINARY_HEADERS_03.TYPE] = { - name: "type", - parser: (v) => v -}; -setterByHeader[Constants.BINARY_HEADERS_03.SPEC_VERSION] = { - name: "specversion", - parser: () => "0.3" -}; -setterByHeader[Constants.BINARY_HEADERS_03.SOURCE] = { - name: "source", - parser: (v) => v -}; -setterByHeader[Constants.BINARY_HEADERS_03.ID] = { - name: "id", - parser: (v) => v -}; -setterByHeader[Constants.BINARY_HEADERS_03.TIME] = { - name: "time", - parser: (v) => new Date(Date.parse(v)) -}; -setterByHeader[Constants.BINARY_HEADERS_03.SCHEMA_URL] = { - name: "schemaurl", - parser: (v) => v -}; -setterByHeader[Constants.HEADER_CONTENT_TYPE] = { - name: "dataContentType", - parser: (v) => v -}; -setterByHeader[Constants.BINARY_HEADERS_03.CONTENT_ENCODING] = { - name: "dataContentEncoding", - parser: (v) => v -}; -setterByHeader[Constants.BINARY_HEADERS_03.SUBJECT] = { - name: "subject", - parser: (v) => v -}; - -// Leaving this in place for now. TODO: fixme -// eslint-disable-next-line -function checkDecorator(payload, headers) { - Object.keys(headers) - .map((header) => header.toLocaleLowerCase("en-US")) - .filter((header) => - header === Constants.BINARY_HEADERS_03.CONTENT_ENCODING) - .filter((header) => !allowedEncodings.includes(headers[header])) - .forEach((header) => { - // TODO: using forEach here seems off - throw new ValidationError("unsupported datacontentencoding"); - }); -} - -// Leaving this in place for now. TODO: fixme -// eslint-disable-next-line -function Receiver(configuration) { - this.receiver = new BinaryHTTPReceiver( - parsersByEncoding, - setterByHeader, - allowedContentTypes, - requiredHeaders, - Spec, - Constants.SPEC_V03, - Constants.BINARY_HEADERS_03.EXTENSIONS_PREFIX, - checkDecorator - ); -} - -Receiver.prototype.check = function(payload, headers) { - this.receiver.check(payload, headers); -}; - -Receiver.prototype.parse = function(payload, headers) { - // firstly specific local checks - this.check(payload, headers); - - return this.receiver.parse(payload, headers); -}; - -module.exports = Receiver; diff --git a/lib/bindings/http/receiver_binary_1.js b/lib/bindings/http/receiver_binary_1.js deleted file mode 100644 index 5a642677..00000000 --- a/lib/bindings/http/receiver_binary_1.js +++ /dev/null @@ -1,78 +0,0 @@ -const { - MIME_JSON, - MIME_OCTET_STREAM, - ENCODING_BASE64, - BINARY_HEADERS_1, - HEADER_CONTENT_TYPE, - SPEC_V1 -} = require("./constants.js"); - -const Spec = require("../../specs/spec_1.js"); -const JSONParser = require("../../formats/json/parser.js"); -const Base64Parser = require("../../formats/base64.js"); -const BinaryHTTPReceiver = require("./receiver_binary.js"); - -const { - isString, - isBase64 -} = require("../../utils/fun.js"); - -const parserByType = { - [MIME_JSON] : new JSONParser(), - [MIME_OCTET_STREAM] : { parse(payload) { return payload; } } -}; - -const parsersByEncoding = { [null] : parserByType, [undefined] : parserByType, [ENCODING_BASE64] : {} }; -parsersByEncoding[ENCODING_BASE64][MIME_JSON] = new JSONParser(new Base64Parser()); -parsersByEncoding[ENCODING_BASE64][MIME_OCTET_STREAM] = { - parse(payload) { return payload; } -}; - -const allowedContentTypes = [MIME_JSON, MIME_OCTET_STREAM]; - -const requiredHeaders = [BINARY_HEADERS_1.TYPE, BINARY_HEADERS_1.SPEC_VERSION, - BINARY_HEADERS_1.SOURCE, BINARY_HEADERS_1.ID]; - -const setterByHeader = { - [BINARY_HEADERS_1.TYPE] : { name: "type", parser: (v) => v }, - [BINARY_HEADERS_1.SPEC_VERSION] : { name: "specversion", parser: () => "1.0" }, - [BINARY_HEADERS_1.SOURCE] : { name: "source", parser: (v) => v }, - [BINARY_HEADERS_1.ID] : { name: "id", parser: (v) => v }, - [BINARY_HEADERS_1.TIME] : { name: "time", parser: (v) => new Date(Date.parse(v)) }, - [BINARY_HEADERS_1.DATA_SCHEMA] : { name: "dataschema", parser: (v) => v }, - [HEADER_CONTENT_TYPE] : { name: "dataContentType", parser: (v) => v }, - [BINARY_HEADERS_1.SUBJECT] : { name: "subject", parser: (v) => v } -}; - -// Leaving these in place for now. TODO: fixme -// eslint-disable-next-line -function checkDecorator(payload, headers) {} - -// Leaving this in place for now. TODO: fixme -// eslint-disable-next-line -function Receiver(configuration) { - this.receiver = new BinaryHTTPReceiver( - parsersByEncoding, - setterByHeader, - allowedContentTypes, - requiredHeaders, - Spec, - SPEC_V1, - BINARY_HEADERS_1.EXTENSIONS_PREFIX, - checkDecorator - ); -} - -Receiver.prototype.check = function(payload, headers) { - this.receiver.check(payload, headers); -}; - -Receiver.prototype.parse = function(payload, headers) { - payload = isString(payload) && isBase64(payload) - ? Buffer.from(payload, "base64").toString() - : payload; - - return this.receiver.parse(payload, headers); -}; - -module.exports = Receiver; diff --git a/lib/bindings/http/receiver_structured.js b/lib/bindings/http/receiver_structured.js index f16f4aac..453786b2 100644 --- a/lib/bindings/http/receiver_structured.js +++ b/lib/bindings/http/receiver_structured.js @@ -1,82 +1,25 @@ -const Constants = require("./constants.js"); -const Commons = require("./commons.js"); -const CloudEvent = require("../../cloudevent.js"); -const ValidationError = require("../../validation_error.js"); - -const { - isDefinedOrThrow, - isStringOrObjectOrThrow -} = require("../../utils/fun.js"); - -function validateArgs(payload, attributes) { - Array.of(payload) - .filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined"))) - .filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or string"))) - .shift(); - - Array.of(attributes) - .filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined"))) - .shift(); -} - -function StructuredHTTPReceiver( - parserByMime, - parserMap, - allowedContentTypes, - Spec) { - this.parserByMime = parserByMime; - this.parserMap = parserMap; - this.allowedContentTypes = allowedContentTypes; - this.Spec = Spec; - this.spec = new Spec(); -} - -StructuredHTTPReceiver.prototype.check = function(payload, headers) { - validateArgs(payload, headers); - - const sanityHeaders = Commons.sanityAndClone(headers); - - // Validation Level 1 - if (!this.allowedContentTypes - .includes(sanityHeaders[Constants.HEADER_CONTENT_TYPE])) { - throw new ValidationError("invalid content type", [sanityHeaders[Constants.HEADER_CONTENT_TYPE]]); - } - - // No erros! Its contains the minimum required attributes -}; - -StructuredHTTPReceiver.prototype.parse = function(payload, headers) { - this.check(payload, headers); - - const sanityHeaders = Commons.sanityAndClone(headers); - - const contentType = sanityHeaders[Constants.HEADER_CONTENT_TYPE]; - - const parser = this.parserByMime[contentType]; - const event = parser.parse(payload); - this.spec.check(event); - - const processedAttributes = []; - const cloudevent = new CloudEvent(this.Spec); - - this.parserMap.forEach((value, key) => { - if (event[key]) { - // invoke the setter function - cloudevent[value.name](value.parser(event[key])); - - // to use ahead, for extensions processing - processedAttributes.push(key); +const ReceiverV1 = require("./v1/receiver_structured_1.js"); +const ReceiverV3 = require("./v03/receiver_structured_0_3.js"); + +const { SPEC_V03, SPEC_V1 } = require("./constants.js"); +const { check, parse } = require("./validation/structured.js"); + +class StructuredHTTPReceiver { + constructor(version = SPEC_V1) { + if (version === SPEC_V1) { + this.receiver = new ReceiverV1(); + } else if (version === SPEC_V03) { + this.receiver = new ReceiverV3(); } - }); + } - // Every unprocessed attribute should be an extension - Array.from(Object.keys(event)) - .filter((attribute) => !processedAttributes.includes(attribute)) - .forEach((extension) => - cloudevent.addExtension(extension, event[extension]) - ); + check(payload, headers) { + return check(payload, headers, this.receiver); + } - return cloudevent; -}; + parse(payload, headers) { + return parse(payload, headers, this.receiver); + } +} module.exports = StructuredHTTPReceiver; diff --git a/lib/bindings/http/unmarshaller.js b/lib/bindings/http/unmarshaller.js deleted file mode 100644 index 9bf97c24..00000000 --- a/lib/bindings/http/unmarshaller.js +++ /dev/null @@ -1,76 +0,0 @@ -const { - HEADER_CONTENT_TYPE, - MIME_CE, - MIME_CE_JSON, - MIME_JSON, - MIME_OCTET_STREAM, - BINARY, - STRUCTURED -} = require("./constants.js"); -const Commons = require("./commons.js"); -const ValidationError = require("../../validation_error.js"); - -const allowedBinaryContentTypes = [ - MIME_JSON, - MIME_OCTET_STREAM -]; - -const allowedStructuredContentTypes = [ - MIME_CE_JSON -]; - -// Is it binary or structured? -function resolveBindingName(payload, headers) { - const contentType = - Commons.sanityContentType(headers[HEADER_CONTENT_TYPE]); - - if (contentType.startsWith(MIME_CE)) { - // Structured - if (allowedStructuredContentTypes.includes(contentType)) { - return STRUCTURED; - } - throwValidationError("structured+type not allowed", contentType); - } else { - // Binary - if (allowedBinaryContentTypes.includes(contentType)) { - return BINARY; - } - throwValidationError("content type not allowed", contentType); - } -} - -function throwValidationError(msg, contentType) { - const err = new ValidationError(msg); - err.errors = [contentType]; - throw err; -} - -class Unmarshaller { - constructor(receiverByBinding) { - this.receiverByBinding = receiverByBinding; - } - - unmarshall(payload, headers) { - if (!payload) { - throw new ValidationError("payload is null or undefined"); - } - if (!headers) { - throw new ValidationError("headers is null or undefined"); - } - - // Validation level 1 - const sanityHeaders = Commons.sanityAndClone(headers); - if (!sanityHeaders[HEADER_CONTENT_TYPE]) { - throw new ValidationError("content-type header not found"); - } - - // Resolve the binding - const bindingName = resolveBindingName(payload, sanityHeaders); - const cloudevent = this.receiverByBinding[bindingName] - .parse(payload, sanityHeaders); - - return cloudevent; - } -} - -module.exports = Unmarshaller; diff --git a/lib/bindings/http/unmarshaller_0_3.js b/lib/bindings/http/unmarshaller_0_3.js deleted file mode 100644 index a6bf300c..00000000 --- a/lib/bindings/http/unmarshaller_0_3.js +++ /dev/null @@ -1,19 +0,0 @@ -const GenericUnmarshaller = require("./unmarshaller.js"); - -const StructuredReceiver = require("./receiver_structured_0_3.js"); -const BinaryReceiver = require("./receiver_binary_0_3.js"); - -const RECEIVER_BY_BINDING = { - structured: new StructuredReceiver(), - binary: new BinaryReceiver() -}; - -const Unmarshaller = function() { - this.unmarshaller = new GenericUnmarshaller(RECEIVER_BY_BINDING); -}; - -Unmarshaller.prototype.unmarshall = function(payload, headers) { - return this.unmarshaller.unmarshall(payload, headers); -}; - -module.exports = Unmarshaller; diff --git a/lib/bindings/http/emitter_binary_0_3.js b/lib/bindings/http/v03/emitter_binary_0_3.js similarity index 96% rename from lib/bindings/http/emitter_binary_0_3.js rename to lib/bindings/http/v03/emitter_binary_0_3.js index 50021528..4ebbff6e 100644 --- a/lib/bindings/http/emitter_binary_0_3.js +++ b/lib/bindings/http/v03/emitter_binary_0_3.js @@ -1,7 +1,7 @@ const { HEADER_CONTENT_TYPE, BINARY_HEADERS_03 -} = require("./constants.js"); +} = require("../constants.js"); const headerByGetter = {}; diff --git a/lib/bindings/http/v03/index.js b/lib/bindings/http/v03/index.js new file mode 100644 index 00000000..9fb13a42 --- /dev/null +++ b/lib/bindings/http/v03/index.js @@ -0,0 +1,9 @@ +const Spec = require("./spec_0_3.js"); +const BinaryEmitter = require("./emitter_binary_0_3.js"); +const StructuredEmitter = require("./receiver_structured_0_3.js"); + +module.exports = { + Spec, + BinaryEmitter, + StructuredEmitter +}; diff --git a/lib/bindings/http/v03/receiver_binary_0_3.js b/lib/bindings/http/v03/receiver_binary_0_3.js new file mode 100644 index 00000000..e6dcb4cb --- /dev/null +++ b/lib/bindings/http/v03/receiver_binary_0_3.js @@ -0,0 +1,96 @@ +const { + SPEC_V03, + MIME_JSON, + MIME_OCTET_STREAM, + ENCODING_BASE64, + HEADER_CONTENT_TYPE, + BINARY_HEADERS_03 +} = require("../constants.js"); +const Spec = require("./spec_0_3.js"); + +const JSONParser = require("../../../formats/json/parser.js"); +const Base64Parser = require("../../../formats/base64.js"); + +const parserByType = { + [MIME_JSON]: new JSONParser(), + [MIME_OCTET_STREAM]: { + parse(payload) { return payload; } + } +}; + +const parsersByEncoding = { + null: parserByType, + undefined: parserByType +}; + +// base64 +parsersByEncoding[ENCODING_BASE64] = { + [MIME_JSON]: new JSONParser(new Base64Parser()), + [MIME_OCTET_STREAM]: { + parse(payload) { return payload; } + } +}; + +const allowedContentTypes = [ + MIME_JSON, MIME_OCTET_STREAM +]; + +const requiredHeaders = [ + BINARY_HEADERS_03.TYPE, + BINARY_HEADERS_03.SPEC_VERSION, + BINARY_HEADERS_03.SOURCE, + BINARY_HEADERS_03.ID +]; + +const setterByHeader = {}; +setterByHeader[BINARY_HEADERS_03.TYPE] = { + name: "type", + parser: (v) => v +}; +setterByHeader[BINARY_HEADERS_03.SPEC_VERSION] = { + name: "specversion", + parser: () => "0.3" +}; +setterByHeader[BINARY_HEADERS_03.SOURCE] = { + name: "source", + parser: (v) => v +}; +setterByHeader[BINARY_HEADERS_03.ID] = { + name: "id", + parser: (v) => v +}; +setterByHeader[BINARY_HEADERS_03.TIME] = { + name: "time", + parser: (v) => new Date(Date.parse(v)) +}; +setterByHeader[BINARY_HEADERS_03.SCHEMA_URL] = { + name: "schemaurl", + parser: (v) => v +}; +setterByHeader[HEADER_CONTENT_TYPE] = { + name: "dataContentType", + parser: (v) => v +}; +setterByHeader[BINARY_HEADERS_03.CONTENT_ENCONDING] = { + name: "dataContentEncoding", + parser: (v) => v +}; +setterByHeader[BINARY_HEADERS_03.SUBJECT] = { + name: "subject", + parser: (v) => v +}; + +class Receiver { + constructor() { + this.parsersByEncoding = parsersByEncoding; + this.setterByHeader = setterByHeader; + this.allowedContentTypes = allowedContentTypes; + this.requiredHeaders = requiredHeaders; + this.extensionsPrefix = BINARY_HEADERS_03.EXTENSIONS_PREFIX; + this.specversion = SPEC_V03; + this.Spec = Spec; + this.spec = new Spec(); + } +} + +module.exports = Receiver; diff --git a/lib/bindings/http/receiver_structured_0_3.js b/lib/bindings/http/v03/receiver_structured_0_3.js similarity index 53% rename from lib/bindings/http/receiver_structured_0_3.js rename to lib/bindings/http/v03/receiver_structured_0_3.js index a9a3f0cd..3dbdac01 100644 --- a/lib/bindings/http/receiver_structured_0_3.js +++ b/lib/bindings/http/v03/receiver_structured_0_3.js @@ -13,21 +13,19 @@ const { SUBJECT, DATA } -} = require("./constants.js"); -const Constants = require("./constants.js"); -const Spec = require("../../specs/spec_0_3.js"); -const JSONParser = require("../../formats/json/parser.js"); - -const StructuredHTTPReceiver = require("./receiver_structured.js"); +} = require("../constants.js"); +const Spec = require("./spec_0_3.js"); +const JSONParser = require("../../../formats/json/parser.js"); const jsonParserSpec = new JSONParser(); +const parserByMime = { + [MIME_JSON]: jsonParserSpec, + [MIME_CE_JSON]: jsonParserSpec +}; -const parserByMime = {}; -parserByMime[MIME_JSON] = jsonParserSpec; -parserByMime[MIME_CE_JSON] = jsonParserSpec; - -const allowedContentTypes = []; -allowedContentTypes.push(MIME_CE_JSON); +const allowedContentTypes = [ + MIME_CE_JSON +]; function parser(name, parser = (v) => v) { return { name: name, parser: parser}; @@ -46,23 +44,14 @@ parserMap.set(CONTENT_TYPE, passThroughParser("dataContentType")); parserMap.set(SUBJECT, passThroughParser("subject")); parserMap.set(DATA, passThroughParser("data")); -// Leaving this in place for now. TODO: fixme -// eslint-disable-next-line -function Receiver(configuration) { - this.receiver = new StructuredHTTPReceiver( - parserByMime, - parserMap, - allowedContentTypes, - Spec - ); +class Receiver { + constructor() { + this.parserByMime = parserByMime; + this.parserMap = parserMap; + this.allowedContentTypes = allowedContentTypes; + this.Spec = Spec; + this.spec = new Spec(); + } } -Receiver.prototype.check = function(payload, headers) { - this.receiver.check(payload, headers); -}; - -Receiver.prototype.parse = function(payload, headers) { - return this.receiver.parse(payload, headers); -}; - module.exports = Receiver; diff --git a/lib/specs/spec_0_3.js b/lib/bindings/http/v03/spec_0_3.js similarity index 97% rename from lib/specs/spec_0_3.js rename to lib/bindings/http/v03/spec_0_3.js index 323aa044..50368ec6 100644 --- a/lib/specs/spec_0_3.js +++ b/lib/bindings/http/v03/spec_0_3.js @@ -1,12 +1,12 @@ const { v4: uuidv4 } = require("uuid"); const Ajv = require("ajv"); -const ValidationError = require("../validation_error.js"); +const ValidationError = require("../validation/validation_error.js"); const { isBase64, clone, asData -} = require("../utils/fun.js"); +} = require("../validation/fun.js"); const RESERVED_ATTRIBUTES = { type: "type", @@ -126,7 +126,7 @@ function Spec03(_caller) { }; if (!_caller) { - _caller = require("../cloudevent.js"); + _caller = require("../../../cloudevent.js"); } /* diff --git a/lib/bindings/http/emitter_binary_1.js b/lib/bindings/http/v1/emitter_binary_1.js similarity index 96% rename from lib/bindings/http/emitter_binary_1.js rename to lib/bindings/http/v1/emitter_binary_1.js index 3c08e952..c88ac6a2 100644 --- a/lib/bindings/http/emitter_binary_1.js +++ b/lib/bindings/http/v1/emitter_binary_1.js @@ -1,7 +1,7 @@ const { HEADER_CONTENT_TYPE, BINARY_HEADERS_1 -} = require("./constants.js"); +} = require("../constants.js"); const headerByGetter = {}; diff --git a/lib/bindings/http/v1/index.js b/lib/bindings/http/v1/index.js new file mode 100644 index 00000000..6773ff44 --- /dev/null +++ b/lib/bindings/http/v1/index.js @@ -0,0 +1,9 @@ +const Spec = require("./spec_1.js"); +const BinaryEmitter = require("./emitter_binary_1.js"); +const StructuredEmitter = require("./receiver_structured_1.js"); + +module.exports = { + Spec, + BinaryEmitter, + StructuredEmitter +}; diff --git a/lib/bindings/http/v1/receiver_binary_1.js b/lib/bindings/http/v1/receiver_binary_1.js new file mode 100644 index 00000000..4052337d --- /dev/null +++ b/lib/bindings/http/v1/receiver_binary_1.js @@ -0,0 +1,55 @@ +const { + SPEC_V1, + MIME_JSON, + MIME_OCTET_STREAM, + ENCODING_BASE64, + BINARY_HEADERS_1, + HEADER_CONTENT_TYPE +} = require("../constants.js"); + +const Spec = require("./spec_1.js"); +const JSONParser = require("../../../formats/json/parser.js"); +const Base64Parser = require("../../../formats/base64.js"); + +const parserByType = { + [MIME_JSON] : new JSONParser(), + [MIME_OCTET_STREAM] : { parse(payload) { return payload; } } +}; + +const parsersByEncoding = { [null] : parserByType, [undefined] : parserByType, [ENCODING_BASE64] : {} }; +parsersByEncoding[ENCODING_BASE64][MIME_JSON] = new JSONParser(new Base64Parser()); +parsersByEncoding[ENCODING_BASE64][MIME_OCTET_STREAM] = { + parse(payload) { return payload; } +}; + +const allowedContentTypes = [MIME_JSON, MIME_OCTET_STREAM]; + +const requiredHeaders = [BINARY_HEADERS_1.TYPE, BINARY_HEADERS_1.SPEC_VERSION, + BINARY_HEADERS_1.SOURCE, BINARY_HEADERS_1.ID]; + +const setterByHeader = { + [BINARY_HEADERS_1.TYPE] : { name: "type", parser: (v) => v }, + [BINARY_HEADERS_1.SPEC_VERSION] : { name: "specversion", parser: () => "1.0" }, + [BINARY_HEADERS_1.SOURCE] : { name: "source", parser: (v) => v }, + [BINARY_HEADERS_1.ID] : { name: "id", parser: (v) => v }, + [BINARY_HEADERS_1.TIME] : { name: "time", parser: (v) => new Date(Date.parse(v)) }, + [BINARY_HEADERS_1.DATA_SCHEMA] : { name: "dataschema", parser: (v) => v }, + [HEADER_CONTENT_TYPE] : { name: "dataContentType", parser: (v) => v }, + [BINARY_HEADERS_1.SUBJECT] : { name: "subject", parser: (v) => v } +}; + +class Receiver { + constructor() { + this.parserByType = parserByType; + this.parsersByEncoding = parsersByEncoding; + this.allowedContentTypes = allowedContentTypes; + this.requiredHeaders = requiredHeaders; + this.setterByHeader = setterByHeader; + this.specversion = SPEC_V1; + this.extensionsPrefix = BINARY_HEADERS_1.EXTENSIONS_PREFIX; + this.Spec = Spec; + this.spec = new Spec(); + } +} + +module.exports = Receiver; diff --git a/lib/bindings/http/receiver_structured_1.js b/lib/bindings/http/v1/receiver_structured_1.js similarity index 52% rename from lib/bindings/http/receiver_structured_1.js rename to lib/bindings/http/v1/receiver_structured_1.js index 62a1a290..0f60ded8 100644 --- a/lib/bindings/http/receiver_structured_1.js +++ b/lib/bindings/http/v1/receiver_structured_1.js @@ -1,5 +1,4 @@ const { - MIME_JSON, MIME_CE_JSON, STRUCTURED_ATTRS_1 : { TYPE, @@ -11,23 +10,22 @@ const { CONTENT_TYPE, SUBJECT, DATA, - DATA_BASE64 + DATA_BASE64, + MIME_JSON } -} = require("./constants.js"); +} = require("../constants.js"); -const Spec = require("../../specs/spec_1.js"); -const JSONParser = require("../../formats/json/parser.js"); - -const StructuredHTTPReceiver = require("./receiver_structured.js"); +const Spec = require("./spec_1.js"); +const JSONParser = require("../../../formats/json/parser.js"); const jsonParserSpec = new JSONParser(); -const parserByMime = {}; -parserByMime[MIME_JSON] = jsonParserSpec; -parserByMime[MIME_CE_JSON] = jsonParserSpec; +const parserByMime = { + [MIME_JSON]: jsonParserSpec, + [MIME_CE_JSON]: jsonParserSpec +}; -const allowedContentTypes = []; -allowedContentTypes.push(MIME_CE_JSON); +const allowedContentTypes = [ MIME_CE_JSON ]; function parser(name, parser = (v) => v) { return { name: name, parser: parser}; @@ -46,23 +44,14 @@ parserMap.set(SUBJECT, passThroughParser("subject")); parserMap.set(DATA, passThroughParser("data")); parserMap.set(DATA_BASE64, passThroughParser("data")); -// Leaving this in place for now. TODO: fixme -// eslint-disable-next-line -function Receiver(configuration) { - this.receiver = new StructuredHTTPReceiver( - parserByMime, - parserMap, - allowedContentTypes, - Spec - ); +class Receiver { + constructor() { + this.parserByMime = parserByMime; + this.parserMap = parserMap; + this.allowedContentTypes = allowedContentTypes; + this.Spec = Spec; + this.spec = new Spec(); + } } -Receiver.prototype.check = function(payload, headers) { - this.receiver.check(payload, headers); -}; - -Receiver.prototype.parse = function(payload, headers) { - return this.receiver.parse(payload, headers); -}; - module.exports = Receiver; diff --git a/lib/specs/spec_1.js b/lib/bindings/http/v1/spec_1.js similarity index 97% rename from lib/specs/spec_1.js rename to lib/bindings/http/v1/spec_1.js index 7f2d628a..942f625c 100644 --- a/lib/specs/spec_1.js +++ b/lib/bindings/http/v1/spec_1.js @@ -1,6 +1,6 @@ const { v4: uuidv4 } = require("uuid"); const Ajv = require("ajv"); -const ValidationError = require("../validation_error.js"); +const ValidationError = require("../validation/validation_error.js"); const { asData, @@ -10,7 +10,7 @@ const { isDate, isBinary, clone -} = require("../utils/fun.js"); +} = require("../validation/fun.js"); const isValidType = (v) => (isBoolean(v) || isInteger(v) || isString(v) || isDate(v) || isBinary(v)); @@ -122,7 +122,7 @@ function Spec1(_caller) { }; if (!_caller) { - _caller = require("../cloudevent.js"); + _caller = require("../../../cloudevent.js"); } /* diff --git a/lib/bindings/http/validation/binary.js b/lib/bindings/http/validation/binary.js new file mode 100644 index 00000000..c97ff174 --- /dev/null +++ b/lib/bindings/http/validation/binary.js @@ -0,0 +1,111 @@ +const CloudEvent = require("../../../cloudevent.js"); + +const { + sanityAndClone, + validateArgs +} = require("./commons.js"); +const ValidationError = require("./validation_error.js"); +const { + HEADER_CONTENT_TYPE, + MIME_JSON, + DEFAULT_SPEC_VERSION_HEADER +} = require("../constants.js"); + +const { + isString, + isObject, + isBase64 +} = require("./fun.js"); + +function check(payload, headers, receiver) { + // Validation Level 0 + validateArgs(payload, headers); + + // The receiver determines the specification version + if (!isObject(receiver)) throw new SyntaxError("no receiver"); + + // Clone and low case all headers names + const sanityHeaders = sanityAndClone(headers); + + // Validation Level 1 - if content-type exists, be sure it's + // an allowed type + const contentTypeHeader = sanityHeaders[HEADER_CONTENT_TYPE]; + const noContentType = !receiver.allowedContentTypes.includes(contentTypeHeader); + if (contentTypeHeader && noContentType) { + throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]); + } + + receiver.requiredHeaders + .filter((required) => !sanityHeaders[required]) + .forEach((required) => { + throw new ValidationError(`header '${required}' not found`); + }); + + if (sanityHeaders[DEFAULT_SPEC_VERSION_HEADER] !== receiver.specversion) { + throw new ValidationError("invalid spec version", [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]]); + } +} + +function parse(payload, headers, receiver) { + payload = isString(payload) && isBase64(payload) + ? Buffer.from(payload, "base64").toString() + : payload; + + check(payload, headers, receiver); + + // Clone and low case all headers names + const sanityHeaders = sanityAndClone(headers); + if (!sanityHeaders[HEADER_CONTENT_TYPE]) { + sanityHeaders[HEADER_CONTENT_TYPE] = MIME_JSON; + } + + const processedHeaders = []; + const cloudevent = new CloudEvent(receiver.Spec); + + const setterByHeader = receiver.setterByHeader; + // dont worry, check() have seen what was required or not + Array.from(Object.keys(setterByHeader)) + .filter((header) => sanityHeaders[header]) + .forEach((header) => { + const setterName = setterByHeader[header].name; + const parserFun = setterByHeader[header].parser; + + // invoke the setter function + cloudevent[setterName](parserFun(sanityHeaders[header])); + + // to use ahead, for extensions processing + processedHeaders.push(header); + }); + + // Parses the payload + const parsedPayload = + parserFor(receiver.parsersByEncoding, cloudevent, sanityHeaders) + .parse(payload); + + // Every unprocessed header can be an extension + Array.from(Object.keys(sanityHeaders)) + .filter((value) => !processedHeaders.includes(value)) + .filter((value) => value.startsWith(receiver.extensionsPrefix)) + .map((extension) => extension.substring(receiver.extensionsPrefix.length) + ).forEach((extension) => cloudevent.addExtension(extension, + sanityHeaders[receiver.extensionsPrefix + extension]) + ); + + // Sets the data + cloudevent.data(parsedPayload); + + // Checks the event spec + cloudevent.format(); + + // return the result + return cloudevent; +} + +function parserFor(parsersByEncoding, cloudevent, headers) { + const encoding = cloudevent.spec.payload.datacontentencoding; + return parsersByEncoding[encoding][headers[HEADER_CONTENT_TYPE]]; +} + +module.exports = { + check, parse +}; \ No newline at end of file diff --git a/lib/bindings/http/commons.js b/lib/bindings/http/validation/commons.js similarity index 52% rename from lib/bindings/http/commons.js rename to lib/bindings/http/validation/commons.js index f0c646d5..df85397f 100644 --- a/lib/bindings/http/commons.js +++ b/lib/bindings/http/validation/commons.js @@ -1,4 +1,10 @@ -const Constants = require("./constants.js"); +const ValidationError = require("./validation_error.js"); +const Constants = require("../constants.js"); +const { + isDefinedOrThrow, + isStringOrObjectOrThrow +} = require("./fun.js"); + // Specific sanity for content-type header function sanityContentType(contentType) { @@ -27,7 +33,19 @@ function sanityAndClone(headers) { return sanityHeaders; } +function validateArgs(payload, attributes) { + Array.of(payload) + .filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined"))) + .filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or a string"))) + .shift(); + + Array.of(attributes) + .filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined"))) + .shift(); +} + module.exports = { sanityAndClone, - sanityContentType + sanityContentType, + validateArgs }; diff --git a/lib/utils/fun.js b/lib/bindings/http/validation/fun.js similarity index 100% rename from lib/utils/fun.js rename to lib/bindings/http/validation/fun.js diff --git a/lib/bindings/http/validation/structured.js b/lib/bindings/http/validation/structured.js new file mode 100644 index 00000000..ba9b7bd7 --- /dev/null +++ b/lib/bindings/http/validation/structured.js @@ -0,0 +1,57 @@ +const CloudEvent = require("../../../cloudevent.js"); +const ValidationError = require("./validation_error.js"); +const { + sanityAndClone, + validateArgs +} = require("./commons.js"); +const { + HEADER_CONTENT_TYPE +} = require("../constants.js"); + +function check(payload, headers, receiver) { + validateArgs(payload, headers); + + const sanityHeaders = sanityAndClone(headers); + + // Validation Level 1 + if (!receiver.allowedContentTypes + .includes(sanityHeaders[HEADER_CONTENT_TYPE])) { + throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]); + } +} + +function parse(payload, headers, receiver) { + check(payload, headers, receiver); + + const sanityHeaders = sanityAndClone(headers); + + const contentType = sanityHeaders[HEADER_CONTENT_TYPE]; + + const parser = receiver.parserByMime[contentType]; + const event = parser.parse(payload); + receiver.spec.check(event); + + const processedAttributes = []; + const cloudevent = new CloudEvent(receiver.Spec); + + receiver.parserMap.forEach((value, key) => { + if (event[key]) { + // invoke the setter function + cloudevent[value.name](value.parser(event[key])); + + // to use ahead, for extensions processing + processedAttributes.push(key); + } + }); + + // Every unprocessed attribute should be an extension + Array.from(Object.keys(event)) + .filter((attribute) => !processedAttributes.includes(attribute)) + .forEach((extension) => + cloudevent.addExtension(extension, event[extension]) + ); + + return cloudevent; +} + +module.exports = { parse, check }; \ No newline at end of file diff --git a/lib/validation_error.js b/lib/bindings/http/validation/validation_error.js similarity index 88% rename from lib/validation_error.js rename to lib/bindings/http/validation/validation_error.js index 745ff08f..307c168e 100644 --- a/lib/validation_error.js +++ b/lib/bindings/http/validation/validation_error.js @@ -7,7 +7,7 @@ class ValidationError extends TypeError { * Constructs a new {ValidationError} with the message * and array of additional errors. * @param {string} message the error message - * @param {[string]|[ErrorObject]} [errors] any additional errors related to validation + * @param {string[]|ErrorObject[]} [errors] any additional errors related to validation */ constructor(message, errors) { super(message); diff --git a/lib/cloudevent.js b/lib/cloudevent.js index 99a2c616..d5f85543 100644 --- a/lib/cloudevent.js +++ b/lib/cloudevent.js @@ -1,4 +1,4 @@ -const Spec = require("./specs/spec_1.js"); +const Spec = require("./bindings/http/v1/spec_1.js"); const Formatter = require("./formats/json/formatter.js"); /** diff --git a/lib/formats/json/parser.js b/lib/formats/json/parser.js index 12efdac6..14f10c37 100644 --- a/lib/formats/json/parser.js +++ b/lib/formats/json/parser.js @@ -2,8 +2,8 @@ const { isString, isDefinedOrThrow, isStringOrObjectOrThrow -} = require("../../utils/fun.js"); -const ValidationError = require("../../validation_error.js"); +} = require("../../bindings/http/validation/fun.js"); +const ValidationError = require("../../bindings/http/validation/validation_error.js"); const invalidPayloadTypeError = new ValidationError("invalid payload type, allowed are: string or object"); const nullOrUndefinedPayload = new ValidationError("null or undefined payload"); diff --git a/package.json b/package.json index 95354518..e472aa68 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,7 @@ "release": "standard-version" }, "files": [ - "lib", - "v03", - "v1" + "lib" ], "standard-version": { "types": [ diff --git a/test/bindings/http/http_emitter_test.js b/test/bindings/http/http_emitter_test.js index ffae9e37..e09760a6 100644 --- a/test/bindings/http/http_emitter_test.js +++ b/test/bindings/http/http_emitter_test.js @@ -11,8 +11,8 @@ const { const { CloudEvent, HTTPEmitter } = require("../../../"); -const V1Spec = require("../../../v1").Spec; -const V03Spec = require("../../../v03").Spec; +const V1Spec = require("../../../lib/bindings/http/v1").Spec; +const V03Spec = require("../../../lib/bindings/http/v03").Spec; const receiver = "https://cloudevents.io/"; const type = "com.example.test"; diff --git a/test/bindings/http/promiscuous_receiver_test.js b/test/bindings/http/promiscuous_receiver_test.js index 1a942f9d..8fe06fa7 100644 --- a/test/bindings/http/promiscuous_receiver_test.js +++ b/test/bindings/http/promiscuous_receiver_test.js @@ -8,7 +8,7 @@ const { BINARY_HEADERS_03, BINARY_HEADERS_1 } = require("../../../lib/bindings/http/constants.js"); -const ValidationError = require("../../../lib/validation_error.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); const receiver = new HTTPReceiver(); const id = "1234"; diff --git a/test/bindings/http/receiver_binary_0_3_tests.js b/test/bindings/http/receiver_binary_0_3_tests.js index 51df0db3..e66e3c8e 100644 --- a/test/bindings/http/receiver_binary_0_3_tests.js +++ b/test/bindings/http/receiver_binary_0_3_tests.js @@ -1,14 +1,14 @@ const expect = require("chai").expect; -const HTTPBinaryReceiver = require("../../../lib/bindings/http/receiver_binary_0_3.js"); -const ValidationError = require("../../../lib/validation_error.js"); +const BinaryHTTPReceiver = require("../../../lib/bindings/http/receiver_binary.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); const { BINARY_HEADERS_03, SPEC_V03, HEADER_CONTENT_TYPE } = require("../../../lib/bindings/http/constants.js"); -const receiver = new HTTPBinaryReceiver(); +const receiver = new BinaryHTTPReceiver(SPEC_V03); describe("HTTP Transport Binding Binary Receiver for CloudEvents v0.3", () => { describe("Check", () => { diff --git a/test/bindings/http/receiver_binary_1_tests.js b/test/bindings/http/receiver_binary_1_tests.js index b43e729d..9458e771 100644 --- a/test/bindings/http/receiver_binary_1_tests.js +++ b/test/bindings/http/receiver_binary_1_tests.js @@ -1,16 +1,15 @@ const expect = require("chai").expect; -const { asBase64 } = require("../../../lib/utils/fun.js"); +const { asBase64 } = require("../../../lib/bindings/http/validation/fun.js"); const { BINARY_HEADERS_1, SPEC_V1, HEADER_CONTENT_TYPE } = require("../../../lib/bindings/http/constants.js"); -const ValidationError = require("../../../lib/validation_error.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); -const HTTPBinaryReceiver = - require("../../../lib/bindings/http/receiver_binary_1.js"); +const BinaryHTTPReceiver = require("../../../lib/bindings/http/receiver_binary.js"); -const receiver = new HTTPBinaryReceiver(); +const receiver = new BinaryHTTPReceiver(SPEC_V1); describe("HTTP Transport Binding Binary Receiver for CloudEvents v1.0", () => { describe("Check", () => { diff --git a/test/bindings/http/receiver_structured_0_3_test.js b/test/bindings/http/receiver_structured_0_3_test.js index 7a0157ef..bd744ddf 100644 --- a/test/bindings/http/receiver_structured_0_3_test.js +++ b/test/bindings/http/receiver_structured_0_3_test.js @@ -1,10 +1,11 @@ const expect = require("chai").expect; -const ValidationError = require("../../../lib/validation_error.js"); -const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured_0_3.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); +const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured.js"); const CloudEvent = require("../../../lib/cloudevent.js"); -const { Spec } = require("../../../v03/index.js"); +const { Spec } = require("../../../lib/bindings/http/v03/index.js"); +const { SPEC_V03 } = require("../../../lib/bindings/http/constants.js"); -const receiver = new HTTPStructuredReceiver(); +const receiver = new HTTPStructuredReceiver(SPEC_V03); const type = "com.github.pull.create"; const source = "urn:event:from:myapi/resourse/123"; @@ -51,7 +52,7 @@ describe("HTTP Transport Binding Structured Receiver CloudEvents v0.3", () => { // act and assert expect(receiver.check.bind(receiver, payload, attributes)) - .to.throw(ValidationError, "payload must be an object or string"); + .to.throw(ValidationError, "payload must be an object or a string"); }); it("Throw error when the content-type is invalid", () => { diff --git a/test/bindings/http/receiver_structured_1_test.js b/test/bindings/http/receiver_structured_1_test.js index 1a8601b8..ceb56baa 100644 --- a/test/bindings/http/receiver_structured_1_test.js +++ b/test/bindings/http/receiver_structured_1_test.js @@ -1,11 +1,12 @@ const expect = require("chai").expect; -const { Spec } = require("../../../v1/index.js"); +const { Spec } = require("../../../lib/bindings/http/v1/index.js"); const { CloudEvent } = require("../../../index.js"); -const { asBase64 } = require("../../../lib/utils/fun.js"); -const ValidationError = require("../../../lib/validation_error.js"); -const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured_1.js"); +const { asBase64 } = require("../../../lib/bindings/http/validation/fun.js"); +const { SPEC_V1 } = require("../../../lib/bindings/http/constants.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); +const HTTPStructuredReceiver = require("../../../lib/bindings/http/receiver_structured.js"); -const receiver = new HTTPStructuredReceiver(); +const receiver = new HTTPStructuredReceiver(SPEC_V1); const type = "com.github.pull.create"; const source = "urn:event:from:myapi/resource/123"; @@ -48,7 +49,7 @@ describe("HTTP Transport Binding Structured Receiver for CloudEvents v1.0", // act and assert expect(receiver.check.bind(receiver, payload, attributes)) - .to.throw(ValidationError, "payload must be an object or string"); + .to.throw(ValidationError, "payload must be an object or a string"); }); it("Throw error when the content-type is invalid", () => { diff --git a/test/bindings/http/unmarshaller_0_3_tests.js b/test/bindings/http/unmarshaller_0_3_tests.js deleted file mode 100644 index 58377dfc..00000000 --- a/test/bindings/http/unmarshaller_0_3_tests.js +++ /dev/null @@ -1,215 +0,0 @@ -const expect = require("chai").expect; -const ValidationError = require("../../../lib/validation_error.js"); -const Unmarshaller = require("../../../lib/bindings/http/unmarshaller_0_3.js"); -const { CloudEvent } = require("../../../index.js"); -const v03 = require("../../../v03/index.js"); - -const type = "com.github.pull.create"; -const source = "urn:event:from:myapi/resourse/123"; -const now = new Date(); -const schemaurl = "http://cloudevents.io/schema.json"; -const subject = "subject.ext"; -const { - BINARY_HEADERS_03, - HEADER_CONTENT_TYPE, - BINARY -} = require("../../../lib/bindings/http/constants.js"); - -const ceContentType = "application/json"; - -const data = { - foo: "bar" -}; - -const un = new Unmarshaller(); - -describe("HTTP Transport Binding Unmarshaller for CloudEvents v0.3", () => { - it("Throw error when payload is null", () => { - expect(() => un.unmarshall(null)).to.throw(ValidationError, "payload is null or undefined"); - }); - - it("Throw error when headers is null", () => { - expect(() => un.unmarshall({})).to.throw(ValidationError, "headers is null or undefined"); - expect(() => un.unmarshall({}, null)).to - .throw(ValidationError, "headers is null or undefined"); - }); - - it("Throw error when there is no content-type header", () => { - expect(() => un.unmarshall({}, {})).to - .throw(ValidationError, "content-type header not found"); - }); - - it("Throw error when content-type is not allowed", () => { - const headers = { - "content-type": "text/xml" - }; - expect(() => un.unmarshall({}, headers)).to - .throw(ValidationError, "content type not allowed"); - }); - - describe("Structured", () => { - it("Throw error when has not allowed mime", () => { - // setup - const headers = { - "content-type": "application/cloudevents+zip" - }; - - // act and assert - expect(() => un.unmarshall({}, headers)).to - .throw(ValidationError, "structured+type not allowed"); - }); - - it("Throw error when the event does not follow the spec 0.3", () => { - const payload = - new CloudEvent(v03.Spec) - .time(now) - .toString(); - - const headers = { - "content-type": "application/cloudevents+json" - }; - - expect(() => un.unmarshall(payload, headers)).to.throw(ValidationError); - }); - - it("Should accept event TypeErrorthat follow the spec 0.3", () => { - const payload = - new CloudEvent(v03.Spec) - .type(type) - .data(data) - .source(source) - .dataContentType(ceContentType) - .time(now) - .schemaurl(schemaurl) - .subject(subject) - .format(); - - const headers = { - "content-type": "application/cloudevents+json" - }; - const event = un.unmarshall(payload, headers); - expect(event instanceof CloudEvent).to.equal(true); - }); - - it("Should parse 'data' stringfied json to json object", () => { - // setup - const payload = - new CloudEvent(v03.Spec) - .type(type) - .source(source) - .dataContentType(ceContentType) - .time(now) - .schemaurl(schemaurl) - .subject(subject) - .data(JSON.stringify(data)) - .toString(); - - const headers = { - "content-type": "application/cloudevents+json" - }; - - const event = un.unmarshall(payload, headers); - expect(event.getData()).to.deep.equal(data); - }); - }); - - describe("Binary", () => { - it("Throw error when has not allowed mime", () => { - // setup - const payload = { - data: "dataString" - }; - const attributes = { - [BINARY_HEADERS_03.TYPE]: "type", - [BINARY_HEADERS_03.SPEC_VERSION]: "0.3", - [BINARY_HEADERS_03.SOURCE]: "source", - [BINARY_HEADERS_03.ID]: "id", - [BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z", - [BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1", - [HEADER_CONTENT_TYPE]: "text/html" - }; - - expect(() => un.unmarshall(payload, attributes)).to - .throw(ValidationError, "content type not allowed"); - }); - - it("Throw error when the event does not follow the spec 0.3", () => { - // setup - const payload = { - data: "dataString" - }; - const attributes = { - [BINARY_HEADERS_03.TYPE]: "type", - "CE-CloudEventsVersion": "0.1", - [BINARY_HEADERS_03.SOURCE]: "source", - [BINARY_HEADERS_03.ID]: "id", - [BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z", - [BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1", - [HEADER_CONTENT_TYPE]: "application/json" - }; - - expect(() => un.unmarshall(payload, attributes)).to - .throw(ValidationError, "header 'ce-specversion' not found"); - }); - - it("No error when all attributes are in place", () => { - // setup - const payload = { - data: "dataString" - }; - const attributes = { - [BINARY_HEADERS_03.TYPE]: "type", - [BINARY_HEADERS_03.SPEC_VERSION]: "0.3", - [BINARY_HEADERS_03.SOURCE]: "source", - [BINARY_HEADERS_03.ID]: "id", - [BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z", - [BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1", - [HEADER_CONTENT_TYPE]: "application/json" - }; - - const event = un.unmarshall(payload, attributes); - expect(event instanceof CloudEvent).to.equal(true); - }); - - it("Throw error when 'ce-datacontentencoding' is not allowed", () => { - // setup - const payload = "eyJtdWNoIjoid293In0="; - - const attributes = { - [BINARY_HEADERS_03.TYPE]: "type", - [BINARY_HEADERS_03.SPEC_VERSION]: "0.3", - [BINARY_HEADERS_03.SOURCE]: "source", - [BINARY_HEADERS_03.ID]: "id", - [BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z", - [BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1", - [HEADER_CONTENT_TYPE]: "application/json", - [BINARY_HEADERS_03.CONTENT_ENCODING]: BINARY - }; - - expect(() => un.unmarshall(payload, attributes)).to - .throw(ValidationError, "unsupported datacontentencoding"); - }); - - it("No error when 'ce-datacontentencoding' is base64", () => { - // setup - const payload = "eyJtdWNoIjoid293In0="; - const expected = { - much: "wow" - }; - - const attributes = { - [BINARY_HEADERS_03.TYPE]: "type", - [BINARY_HEADERS_03.SPEC_VERSION]: "0.3", - [BINARY_HEADERS_03.SOURCE]: "source", - [BINARY_HEADERS_03.ID]: "id", - [BINARY_HEADERS_03.TIME]: "2019-06-16T11:42:00Z", - [BINARY_HEADERS_03.SCHEMA_URL]: "http://schema.registry/v1", - [HEADER_CONTENT_TYPE]: "application/json", - [BINARY_HEADERS_03.CONTENT_ENCODING]: "base64" - }; - - const event = un.unmarshall(payload, attributes); - expect(event.getData()).to.deep.equal(expected); - }); - }); -}); diff --git a/test/formats/json/parser_test.js b/test/formats/json/parser_test.js index e0a7dbea..a31cdd7b 100644 --- a/test/formats/json/parser_test.js +++ b/test/formats/json/parser_test.js @@ -1,6 +1,6 @@ const expect = require("chai").expect; const Parser = require("../../../lib/formats/json/parser.js"); -const ValidationError = require("../../../lib/validation_error.js"); +const ValidationError = require("../../../lib/bindings/http/validation/validation_error.js"); describe("JSON Event Format Parser", () => { it("Throw error when payload is an integer", () => { diff --git a/test/fun_tests.js b/test/fun_tests.js index 3a042a56..b73ba929 100644 --- a/test/fun_tests.js +++ b/test/fun_tests.js @@ -1,5 +1,5 @@ const expect = require("chai").expect; -const fun = require("../lib/utils/fun.js"); +const fun = require("../lib/bindings/http/validation/fun.js"); describe("Functional approach", () => { describe("isStringOrThrow", () => { diff --git a/test/http_binding_0_3.js b/test/http_binding_0_3.js index 97d125e3..f85d36b8 100644 --- a/test/http_binding_0_3.js +++ b/test/http_binding_0_3.js @@ -3,7 +3,7 @@ const nock = require("nock"); 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 v03 = require("../lib/bindings/http/v03/index.js"); const { SPEC_V03 } = require("../lib/bindings/http/constants.js"); diff --git a/test/http_binding_1.js b/test/http_binding_1.js index da640f6c..29535bb1 100644 --- a/test/http_binding_1.js +++ b/test/http_binding_1.js @@ -1,12 +1,12 @@ const expect = require("chai").expect; const nock = require("nock"); const https = require("https"); -const { asBase64 } = require("../lib/utils/fun.js"); +const { asBase64 } = require("../lib/bindings/http/validation/fun.js"); const { SPEC_V1 } = require("../lib/bindings/http/constants.js"); -const { Spec } = require("../v1/index.js"); +const { Spec } = require("../lib/bindings/http/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"); diff --git a/test/sdk_test.js b/test/sdk_test.js index 7ef660c8..b3f74baa 100644 --- a/test/sdk_test.js +++ b/test/sdk_test.js @@ -1,7 +1,7 @@ const expect = require("chai").expect; const { CloudEvent, HTTPReceiver, HTTPEmitter } = require("../"); -const SpecV03 = require("../v03").Spec; -const SpecV1 = require("../v1").Spec; +const SpecV03 = require("../lib/bindings/http/v03").Spec; +const SpecV1 = require("../lib/bindings/http/v1").Spec; const { SPEC_V03, SPEC_V1 diff --git a/test/spec_0_3_tests.js b/test/spec_0_3_tests.js index 6ab6a244..86ddd8c8 100644 --- a/test/spec_0_3_tests.js +++ b/test/spec_0_3_tests.js @@ -1,5 +1,5 @@ const expect = require("chai").expect; -const Spec03 = require("../lib/specs/spec_0_3.js"); +const Spec03 = require("../lib/bindings/http/v03/spec_0_3.js"); const { CloudEvent } = require("../index.js"); const { MIME_JSON, @@ -7,7 +7,7 @@ const { SPEC_V03, BINARY } = require("../lib/bindings/http/constants.js"); -const ValidationError = require("../lib/validation_error.js"); +const ValidationError = require("../lib/bindings/http/validation/validation_error.js"); const id = "97699ec2-a8d9-47c1-bfa0-ff7aa526f838"; const type = "com.github.pull.create"; diff --git a/test/spec_1_tests.js b/test/spec_1_tests.js index 23f5aeef..e6a065f5 100644 --- a/test/spec_1_tests.js +++ b/test/spec_1_tests.js @@ -1,9 +1,9 @@ const expect = require("chai").expect; -const Spec1 = require("../lib/specs/spec_1.js"); +const Spec1 = require("../lib/bindings/http/v1/spec_1.js"); const { CloudEvent } = require("../index.js"); const { v4: uuidv4 } = require("uuid"); -const { asBase64 } = require("../lib/utils/fun.js"); -const ValidationError = require("../lib/validation_error.js"); +const { asBase64 } = require("../lib/bindings/http/validation/fun.js"); +const ValidationError = require("../lib/bindings/http/validation/validation_error.js"); const id = uuidv4(); const type = "com.github.pull.create"; diff --git a/v03/index.js b/v03/index.js deleted file mode 100644 index 40ec1cbc..00000000 --- a/v03/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const Spec = require("../lib/specs/spec_0_3.js"); - -module.exports = { - Spec -}; diff --git a/v1/index.js b/v1/index.js deleted file mode 100644 index e8330bcb..00000000 --- a/v1/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const Spec = require("../lib/specs/spec_1.js"); - -module.exports = { - Spec -};