diff --git a/lib/bindings/http/unmarshaller.js b/lib/bindings/http/unmarshaller.js new file mode 100644 index 0000000..3b7cf6d --- /dev/null +++ b/lib/bindings/http/unmarshaller.js @@ -0,0 +1,84 @@ +var StructuredReceiver = require("./receiver_structured_0_2.js"); +var BinaryReceiver = require("./receiver_binary_0_2.js"); + +const Constants = require("./constants.js"); +const Commons = require("./commons.js"); + +const STRUCTURED = "structured"; +const BINARY = "binary"; + +const receiverByBinding = { + structured : new StructuredReceiver(), + binary : new BinaryReceiver(), +}; + +const allowedBinaryContentTypes = []; +allowedBinaryContentTypes.push(Constants.MIME_JSON); +allowedBinaryContentTypes.push(Constants.MIME_OCTET_STREAM); + +const allowedStructuredContentTypes = []; +allowedStructuredContentTypes.push(Constants.MIME_CE_JSON); + +function validateArgs(payload, headers) { + if(!payload){ + throw {message: "payload is null or undefined"}; + } + + if(!headers){ + throw {message: "headers is null or undefined"}; + } +} + +// Is it binary or structured? +function resolveBindingName(payload, headers) { + + var contentType = + Commons.sanityContentType(headers[Constants.HEADER_CONTENT_TYPE]); + + if(contentType.startsWith(Constants.MIME_CE)){ + // Structured + if(allowedStructuredContentTypes.includes(contentType)){ + return STRUCTURED; + } else { + throw {message: "structured+type not allowed", errors: [contentType]}; + } + } else { + // Binary + if(allowedBinaryContentTypes.includes(contentType)){ + return BINARY; + } else { + throw {message: "content type not allowed", errors : [contentType]}; + } + } +} + +var Unmarshaller = function(receiverByBinding) { + this.receiverByBinding = receiverByBinding; +}; + +Unmarshaller.prototype.unmarshall = function(payload, headers) { + return new Promise((resolve, reject) => { + try { + validateArgs(payload, headers); + + var sanityHeaders = Commons.sanityAndClone(headers); + + // Validation level 1 + if(!sanityHeaders[Constants.HEADER_CONTENT_TYPE]){ + throw {message: "content-type header not found"}; + } + + // Resolve the binding + var bindingName = resolveBindingName(payload, sanityHeaders); + + var cloudevent = + this.receiverByBinding[bindingName].parse(payload, sanityHeaders); + + resolve(cloudevent); + }catch(e){ + reject(e); + } + }); +}; + +module.exports = Unmarshaller; diff --git a/lib/bindings/http/unmarshaller_0_3.js b/lib/bindings/http/unmarshaller_0_3.js new file mode 100644 index 0000000..9f69b49 --- /dev/null +++ b/lib/bindings/http/unmarshaller_0_3.js @@ -0,0 +1,19 @@ +const GenericUnmarshaller = require("./unmarshaller.js"); + +var StructuredReceiver = require("./receiver_structured_0_3.js"); +var BinaryReceiver = require("./receiver_binary_0_3.js"); + +const RECEIVER_BY_BINDING = { + structured : new StructuredReceiver(), + binary : new BinaryReceiver(), +}; + +var 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/test/bindings/http/unmarshaller_0_3_tests.js b/test/bindings/http/unmarshaller_0_3_tests.js new file mode 100644 index 0000000..b95f833 --- /dev/null +++ b/test/bindings/http/unmarshaller_0_3_tests.js @@ -0,0 +1,214 @@ +var expect = require("chai").expect; +var Unmarshaller = require("../../../lib/bindings/http/unmarshaller_0_3.js"); +var Cloudevent = require("../../../index.js"); +var {Spec03} = require("../../../v03/index.js"); + +const type = "com.github.pull.create"; +const source = "urn:event:from:myapi/resourse/123"; +const webhook = "https://cloudevents.io/webhook"; +const contentType = "application/cloudevents+json; charset=utf-8"; +const now = new Date(); +const schemaurl = "http://cloudevents.io/schema.json"; + +const ceContentType = "application/json"; + +const data = { + foo: "bar" +}; + +describe("HTTP Transport Binding Unmarshaller for CloudEvents v0.3", () => { + + it("Throw error when payload is null", () => { + // setup + var payload = null; + var un = new Unmarshaller(); + + // act and assert + return un.unmarshall(payload) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("payload is null or undefined")); + }); + + it("Throw error when headers is null", () => { + // setup + var payload = {}; + var headers = null; + var un = new Unmarshaller(); + + // act and assert + return un.unmarshall(payload, headers) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("headers is null or undefined")); + }); + + it("Throw error when there is no content-type header", () => { + // setup + var payload = {}; + var headers = {}; + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, headers) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("content-type header not found")); + }); + + it("Throw error when content-type is not allowed", () => { + // setup + var payload = {}; + var headers = { + "content-type":"text/xml" + }; + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, headers) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("content type not allowed")); + }); + + describe("Structured", () => { + it("Throw error when has not allowed mime", () => { + // setup + var payload = {}; + var headers = { + "content-type":"application/cloudevents+zip" + }; + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, headers) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("structured+type not allowed")); + }); + + it("Throw error when the event does not follow the spec 0.3", () => { + // setup + var payload = + new Cloudevent() + .type(type) + .source(source) + .contenttype(ceContentType) + .time(now) + .schemaurl(schemaurl) + .data(data) + .toString(); + + var headers = { + "content-type":"application/cloudevents+json" + }; + + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, headers) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("invalid payload")); + }); + + it("Should accept event that follow the spec 0.3", () => { + // setup + var payload = + new Cloudevent(Spec03) + .type(type) + .source(source) + .contenttype(ceContentType) + .time(now) + .schemaurl(schemaurl) + .data(data) + .toString(); + + var headers = { + "content-type":"application/cloudevents+json" + }; + + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, headers) + .then(actual => + expect(actual).to.be.an("object")); + + }); + }); + + describe("Binary", () => { + it("Throw error when has not allowed mime", () => { + // setup + var payload = { + "data" : "dataString" + }; + var attributes = { + "ce-type" : "type", + "ce-specversion" : "0.3", + "ce-source" : "source", + "ce-id" : "id", + "ce-time" : "2019-06-16T11:42:00Z", + "ce-schemaurl" : "http://schema.registry/v1", + "Content-Type" : "text/html" + }; + + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, attributes) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.equal("content type not allowed")); + + }); + + it("Throw error when the event does not follow the spec 0.3", () => { + // setup + var payload = { + "data" : "dataString" + }; + var attributes = { + "ce-type" : "type", + "CE-CloudEventsVersion" : "0.1", + "ce-source" : "source", + "ce-id" : "id", + "ce-time" : "2019-06-16T11:42:00Z", + "ce-schemaurl" : "http://schema.registry/v1", + "Content-Type" : "application/json" + }; + + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, attributes) + .then(actual => {throw {message: "failed"}}) + .catch(err => + expect(err.message).to.not.empty); + }); + + it("No error when all attributes are in place", () => { + // setup + var payload = { + "data" : "dataString" + }; + var attributes = { + "ce-type" : "type", + "ce-specversion" : "0.3", + "ce-source" : "source", + "ce-id" : "id", + "ce-time" : "2019-06-16T11:42:00Z", + "ce-schemaurl" : "http://schema.registry/v1", + "Content-Type" : "application/json" + }; + + var un = new Unmarshaller(); + + // act and assert + un.unmarshall(payload, attributes) + .then(actual => expect(actual).to.be.an("object")); + + }); + }); +});