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 | 58 +- 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 | 176 +--- lib/bindings/http/receiver_binary_0_3.js | 119 --- lib/bindings/http/receiver_binary_1.js | 78 -- lib/bindings/http/receiver_structured.js | 93 +- 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, 4591 insertions(+), 898 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 (81%) 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 0000000..a32a899 --- /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 0000000..2e185d2 --- /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 3d08a13..a661f10 100644 --- a/docs/CloudEvent.html +++ b/docs/CloudEvent.html @@ -73,7 +73,7 @@ -

Classes

+

Classes

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

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

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

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 0000000..0e10683 --- /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 d34ea68..82636f2 100644 --- a/docs/HTTPReceiver.html +++ b/docs/HTTPReceiver.html @@ -73,7 +73,7 @@ -

Classes

+

Classes

@@ -171,7 +171,7 @@
@@ -371,7 +371,7 @@ binary and structured incoming CloudEvents.

diff --git a/docs/StructuredHTTPEmitter.html b/docs/StructuredHTTPEmitter.html new file mode 100644 index 0000000..e8e8dde --- /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 0000000..827aaa4 --- /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 0000000..b06d979 --- /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 0000000..0e6144c --- /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 0000000..7947483 --- /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 9966ac6..35c9703 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 @@ 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)
       }
     };
   }
@@ -148,31 +153,29 @@ class HTTPReceiver {
 }
 
 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;
   }
-  return mode;
+  if (headers[BINARY_HEADERS_1.ID]) {
+    return BINARY;
+  }
+  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 0000000..2a8814d
--- /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 0000000..b6b7166 --- /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 d9412f5..0339974 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 @@ const Formatter = require("./formats/json/formatter.js");
 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 @@ class CloudEvent {
   }
 
   /**
-   * 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 @@ class CloudEvent {
   /**
    * 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 3121598..4783986 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 f8b5e85..3dc53b0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -73,7 +73,7 @@ -

Classes

+

Classes

@@ -136,23 +136,57 @@ const receivedEvent = receiver.accept(req.body, req.headers); 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");
+

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");
 
-const myevent = new CloudEvent()
-  .type("com.github.pull.create")
-  .source("urn:event:from:myapi/resource/123");
+// With only an endpoint URL, this creates a v1 emitter
+const v1Emitter = new HTTPEmitter({
+  url: "https://cloudevents.io/example"
+});
+const event = new CloudEvent()
+  .type(type)
+  .source(source)
+  .time(new Date())
+  .data(data)
 
-const emitter = new StructuredHTTPEmitter({
-  method: "POST",
-  url   : "https://myserver.com"
+// By default, the emitter will send binary events
+v1Emitter.send(event).then((response) => {
+    // handle the response
+  }).catch(console.error);
+
+// To send a structured event, just add that as an option
+v1Emitter.send(event, { mode: "structured" })
+  .then((response) => {
+    // handle the response
+  }).catch(console.error);
+
+// To send an event to an alternate URL, add that as an option
+v1Emitter.send(event, { url: "https://alternate.com/api" })
+  .then((response) => {
+    // handle the response
+  }).catch(console.error);
+
+// Sending a v0.3 event works the same, just let the emitter know when
+// you create it that you are working with the 0.3 spec
+const v03Emitter = new HTTPEmitter({
+  url: "https://cloudevents.io/example",
+  version: "0.3"
 });
 
-// Emit the event
-emitter.emit(myevent)
+// Again, the default is to send binary events
+// To send a structured event or to an alternate URL, provide those
+// as parameters in a options object as above
+v3Emitter.send(event)
+  .then((response) => {
+    // handle the response
+  }).catch(console.error);
+
 

Supported specification features

diff --git a/docs/validation_error.js.html b/docs/validation_error.js.html new file mode 100644 index 0000000..332a515 --- /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 5bed37d..ccd9333 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 78f5631..5553710 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 82087a1..f37ccd5 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 ReceiverV1 = require("./v1/receiver_binary_1.js"); +const ReceiverV3 = require("./v03/receiver_binary_0_3.js"); -const { - isDefinedOrThrow, - isStringOrObjectOrThrow -} = require("../../utils/fun.js"); +const { SPEC_V03, SPEC_V1 } = require("./constants.js"); +const { check, parse } = require("./validation/binary.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(); +/** + * 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(); + } + } - Array.of(attributes) - .filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined"))) - .shift(); + /** + * 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); + } } -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); - } - - // 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]]); - } - - 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]]); - } - - // 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 943adb3..0000000 --- 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 5a64267..0000000 --- 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 f16f4aa..453786b 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 ReceiverV1 = require("./v1/receiver_structured_1.js"); +const ReceiverV3 = require("./v03/receiver_structured_0_3.js"); -const { - isDefinedOrThrow, - isStringOrObjectOrThrow -} = require("../../utils/fun.js"); +const { SPEC_V03, SPEC_V1 } = require("./constants.js"); +const { check, parse } = require("./validation/structured.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]]); +class StructuredHTTPReceiver { + constructor(version = SPEC_V1) { + if (version === SPEC_V1) { + this.receiver = new ReceiverV1(); + } else if (version === SPEC_V03) { + this.receiver = new ReceiverV3(); + } } - // No erros! Its contains the minimum required attributes -}; + check(payload, headers) { + return check(payload, headers, this.receiver); + } -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); - } - }); - - // 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; -}; + 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 9bf97c2..0000000 --- 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 a6bf300..0000000 --- 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 5002152..4ebbff6 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 0000000..9fb13a4 --- /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 0000000..e6dcb4c --- /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 a9a3f0c..3dbdac0 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 323aa04..50368ec 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 3c08e95..c88ac6a 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 0000000..6773ff4 --- /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 0000000..4052337 --- /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 62a1a29..0f60ded 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 7f2d628..942f625 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 0000000..c97ff17 --- /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 f0c646d..df85397 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 0000000..ba9b7bd --- /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 81% rename from lib/validation_error.js rename to lib/bindings/http/validation/validation_error.js index 745ff08..307c168 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 99a2c61..d5f8554 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 12efdac..14f10c3 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 9535451..e472aa6 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 ffae9e3..e09760a 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 1a942f9..8fe06fa 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 51df0db..e66e3c8 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 b43e729..9458e77 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 7a0157e..bd744dd 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 1a8601b..ceb56ba 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 58377df..0000000 --- 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 e0a7dbe..a31cdd7 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 3a042a5..b73ba92 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 97d125e..f85d36b 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 da640f6..29535bb 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 7ef660c..b3f74ba 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 6ab6a24..86ddd8c 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 23f5aee..e6a065f 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 40ec1cb..0000000 --- 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 e8330bc..0000000 --- a/v1/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const Spec = require("../lib/specs/spec_1.js"); - -module.exports = { - Spec -};