From b8f1012d1d76b0c0997c596db31809b8857ba2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabio=20Jos=C3=A9?= Date: Sun, 28 Jul 2019 21:53:34 -0300 Subject: [PATCH] Implementing the spec 0.3 attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fabio José --- ext/spec_0_3.json | 88 ++++++++++++++++++++ lib/specs/spec_0_3.js | 184 +++++++++++++++++++++++++++++++++++++++++ test/spec_0_3_tests.js | 171 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 443 insertions(+) create mode 100644 ext/spec_0_3.json create mode 100644 lib/specs/spec_0_3.js create mode 100644 test/spec_0_3_tests.js diff --git a/ext/spec_0_3.json b/ext/spec_0_3.json new file mode 100644 index 0000000..2c36269 --- /dev/null +++ b/ext/spec_0_3.json @@ -0,0 +1,88 @@ +{ + "$ref": "#/definitions/event", + "definitions": { + "specversion": { + "type": "string", + "minLength": 1, + "const": "0.3" + }, + "datacontenttype": { + "type": "string" + }, + "data": { + "type": [ + "object", + "string" + ] + }, + "event": { + "properties": { + "specversion": { + "$ref": "#/definitions/specversion" + }, + "datacontenttype": { + "$ref": "#/definitions/datacontenttype" + }, + "data": { + "$ref": "#/definitions/data" + }, + "id": { + "$ref": "#/definitions/id" + }, + "time": { + "$ref": "#/definitions/time" + }, + "schemaurl": { + "$ref": "#/definitions/schemaurl" + }, + "subject": { + "$ref": "#/definitions/subject" + }, + "type": { + "$ref": "#/definitions/type" + }, + "extensions": { + "$ref": "#/definitions/extensions" + }, + "source": { + "$ref": "#/definitions/source" + } + }, + "required": [ + "specversion", + "id", + "type", + "source" + ], + "type": "object" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "time": { + "format": "date-time", + "type": "string" + }, + "schemaurl": { + "type": "string", + "format": "uri-reference" + }, + "subject": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "minLength": 1 + }, + "extensions": { + "type": "object" + }, + "source": { + "format": "uri-reference", + "type": "string" + } + }, + "type": "object" +} diff --git a/lib/specs/spec_0_3.js b/lib/specs/spec_0_3.js new file mode 100644 index 0000000..11a0257 --- /dev/null +++ b/lib/specs/spec_0_3.js @@ -0,0 +1,184 @@ +const uuid = require("uuid/v4"); +const empty = require("is-empty"); +const Ajv = require("ajv"); + +// Reserved attributes names +const reserved = { + type: "type", + specversion: "specversion", + source: "source", + id: "id", + time: "time", + schemaurl: "schemaurl", + datacontentencoding: "datacontentencoding", + datacontenttype: "datacontenttype", + subject : "subject", + data: "data" +}; + +const schema = require("../../ext/spec_0_3.json"); + +const ajv = new Ajv({ + extendRefs: true +}); + +const validate = ajv.compile(schema); + +function Spec03(_caller){ + this.payload = { + specversion: "0.3", + id: uuid() + }; + + /* + * Used to inject compatibility methods or attributes + */ + this.caller = _caller; + + /* + * Inject compatibility methods + */ + this.caller.prototype.dataContentEncoding = function(encoding){ + this.spec.dataContentEncoding(encoding); + return this; + }; + this.caller.prototype.getDataContentEncoding = function(){ + return this.spec.getDataContentEncoding(); + }; + + this.caller.prototype.dataContentType = function(contentType){ + this.spec.dataContentType(contentType); + return this; + }; + this.caller.prototype.getDataContentType = function(){ + return this.spec.getDataContentType(); + }; + + this.caller.prototype.subject = function(_subject){ + this.spec.subject(_subject); + return this; + }; + this.caller.prototype.getSubject = function(){ + return this.spec.getSubject(); + }; +} + +/* + * Check the spec constraints + */ +Spec03.prototype.check = function(ce){ + var toCheck = ce; + if(!toCheck) { + toCheck = this.payload; + } + var valid = validate(toCheck); + + if(!valid) { + throw {message: "invalid payload", errors: validate.errors}; + } +}; + +Spec03.prototype.id = function(_id){ + this.payload["id"] = _id; + return this; +}; + +Spec03.prototype.getId = function() { + return this.payload["id"]; +}; + +Spec03.prototype.source = function(_source){ + this.payload["source"] = _source; + return this; +}; + +Spec03.prototype.getSource = function() { + return this.payload["source"]; +}; + +Spec03.prototype.specversion = function(_specversion){ + // does not set! This is right + return this; +}; + +Spec03.prototype.getSpecversion = function() { + return this.payload["specversion"]; +}; + +Spec03.prototype.type = function(_type){ + this.payload["type"] = _type; + return this; +}; + +Spec03.prototype.getType = function(){ + return this.payload["type"]; +}; + +Spec03.prototype.dataContentEncoding = function(encoding) { + this.payload["datacontentencoding"] = encoding; + return this; +}; + +Spec03.prototype.getDataContentEncoding = function() { + return this.payload["datacontentencoding"]; +}; + +// maps to datacontenttype +Spec03.prototype.contenttype = function(_contenttype){ + this.payload["datacontenttype"] = _contenttype; + return this; +}; +Spec03.prototype.getContenttype = function() { + return this.payload["datacontenttype"]; +}; + +Spec03.prototype.dataContentType = function(_contenttype){ + this.payload["datacontenttype"] = _contenttype; + return this; +}; +Spec03.prototype.getDataContentType = function() { + return this.payload["datacontenttype"]; +}; + +Spec03.prototype.schemaurl = function(_schemaurl){ + this.payload["schemaurl"] = _schemaurl; + return this; +}; +Spec03.prototype.getSchemaurl = function() { + return this.payload["schemaurl"]; +}; + +Spec03.prototype.subject = function(_subject){ + this.payload["subject"] = _subject; + return this; +}; +Spec03.prototype.getSubject = function() { + return this.payload["subject"]; +}; + +Spec03.prototype.time = function(_time){ + this.payload["time"] = _time.toISOString(); + return this; +}; +Spec03.prototype.getTime = function() { + return this.payload["time"]; +}; + +Spec03.prototype.data = function(_data){ + this.payload["data"] = _data; + return this; +}; +Spec03.prototype.getData = function() { + return this.payload["data"]; +}; + +Spec03.prototype.addExtension = function(key, value){ + if(!reserved.hasOwnProperty(key)){ + this.payload[key] = value; + } else { + throw {message: "Reserved attribute name: '" + key + "'"}; + } + return this; +}; + +module.exports = Spec03; diff --git a/test/spec_0_3_tests.js b/test/spec_0_3_tests.js new file mode 100644 index 0000000..a4fe982 --- /dev/null +++ b/test/spec_0_3_tests.js @@ -0,0 +1,171 @@ +const expect = require("chai").expect; +const Spec03 = require("../lib/specs/spec_0_3.js"); +const Cloudevent = require("../index.js"); +const uuid = require("uuid/v4"); + +const id = uuid(); +const type = "com.github.pull.create"; +const source = "urn:event:from:myapi/resourse/123"; +const time = new Date(); +const schemaurl = "http://example.com/registry/myschema.json"; +const dataContentEncoding = "base64"; +const dataContentType = "application/json"; +const data = { + much : "wow" +}; +const extensions = {}; +const subject = "subject-x0"; + +var cloudevent = + new Cloudevent(Spec03) + .id(id) + .source(source) + .type(type) + .dataContentEncoding(dataContentEncoding) + .dataContentType(dataContentType) + .schemaurl(schemaurl) + .subject(subject) + .time(time) + .data(data); + +describe("CloudEvents Spec v0.3", () => { + + describe("REQUIRED Attributes", () => { + it("Should have 'id'", () => { + expect(cloudevent.getId()).to.equal(id); + }); + + it("Should have 'source'", () => { + expect(cloudevent.getSource()).to.equal(source); + }); + + it("Should have 'specversion'", () => { + expect(cloudevent.getSpecversion()).to.equal("0.3"); + }); + + it("Should have 'type'", () => { + expect(cloudevent.getType()).to.equal(type); + }); + }); + + describe("OPTIONAL Attributes", () => { + it("Should have 'datacontentencoding'", () => { + expect(cloudevent.getDataContentEncoding()).to.equal(dataContentEncoding); + }); + + it("Should have 'datacontenttype'", () => { + expect(cloudevent.getDataContentType()).to.equal(dataContentType); + }); + + it("contenttype() method should maps to 'datacontenttype'", () => { + cloudevent.contenttype("text/xml"); + expect(cloudevent.getDataContentType()).to.equal("text/xml"); + cloudevent.contenttype(dataContentType); + }); + + it("Should have 'schemaurl'", () => { + expect(cloudevent.getSchemaurl()).to.equal(schemaurl); + }); + + it("Should have 'subject'", () => { + expect(cloudevent.getSubject()).to.equal(subject); + }); + + it("Should have 'time'", () => { + expect(cloudevent.getTime()).to.equal(time.toISOString()); + }); + + it("Should have 'data'", () => { + expect(cloudevent.getData()).to.deep.equal(data); + }); + + it("Should have the 'extension1'", () => { + cloudevent.addExtension("extension1", "value1"); + expect(cloudevent.spec.payload["extension1"]) + .to.equal("value1"); + }); + + it("should throw an error when use a reserved name as extension", () => { + expect(cloudevent.addExtension.bind(cloudevent, "id")) + .to.throw("Reserved attribute name: 'id'"); + }); + }); + + describe("The Constraints check", () => { + describe("'id'", () => { + it("should throw an error when is absent", () => { + delete cloudevent.spec.payload.id; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.id = id; + }); + + it("should throw an erro when is empty", () => { + cloudevent.spec.payload.id = ""; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.id = id; + }); + }); + + describe("'source'", () => { + it("should throw an error when is absent", () => { + delete cloudevent.spec.payload.source; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.source = source; + }); + }); + + describe("'specversion'", () => { + it("should throw an error when is absent", () => { + delete cloudevent.spec.payload.specversion; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.specversion = "0.3"; + }); + + it("should throw an erro when is empty", () => { + cloudevent.spec.payload.specversion = ""; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.specversion = "0.3"; + }); + }); + + describe("'type'", () => { + it("should throw an error when is absent", () => { + delete cloudevent.spec.payload.type; + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.spec.payload.type = type; + }); + + it("should throw an error when is an empty string", () => { + cloudevent.type(""); + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.type(type); + }); + + it("must be a non-empty string", () => { + cloudevent.type(type); + expect(cloudevent.spec.payload.type).to.equal(type); + }); + }); + + describe("'datacontentencoding'", () => { + it("should throw an erro when 'data' is not a string", () => { + + }); + }); + }); + +});