diff --git a/lib/specs/spec_0_3.js b/lib/specs/spec_0_3.js index 11a0257..371a412 100644 --- a/lib/specs/spec_0_3.js +++ b/lib/specs/spec_0_3.js @@ -2,8 +2,11 @@ const uuid = require("uuid/v4"); const empty = require("is-empty"); const Ajv = require("ajv"); -// Reserved attributes names -const reserved = { +const { + equalsOrThrow +} = require("../utils/fun.js"); + +const RESERVED_ATTRIBUTES = { type: "type", specversion: "specversion", source: "source", @@ -16,13 +19,16 @@ const reserved = { data: "data" }; +const SUPPORTED_CONTENT_ENCODING = []; +SUPPORTED_CONTENT_ENCODING.push("base64"); + const schema = require("../../ext/spec_0_3.json"); const ajv = new Ajv({ extendRefs: true }); -const validate = ajv.compile(schema); +const isValidAgainstSchema = ajv.compile(schema); function Spec03(_caller){ this.payload = { @@ -67,14 +73,31 @@ function Spec03(_caller){ * Check the spec constraints */ Spec03.prototype.check = function(ce){ - var toCheck = ce; - if(!toCheck) { - toCheck = this.payload; - } - var valid = validate(toCheck); + var toCheck = (!ce ? this.payload : ce); + + var valid = isValidAgainstSchema(toCheck); + + Array.of(toCheck) + .filter((tc) => (typeof tc.data) !== 'string') + .filter((tc) => tc["datacontentencoding"]) + .forEach((tc) => { + throw {message: "invalid payload", errors: [ + "Use 'datacontentencoding' when 'data' is a string" + ]}; + }); + + Array.of(toCheck) + .filter((tc) => tc["datacontentencoding"]) + .map((tc) => tc["datacontentencoding"].toLocaleLowerCase("en-US")) + .filter((dce) => !SUPPORTED_CONTENT_ENCODING.includes(dce)) + .forEach((dce) => { + throw {message: "invalid payload", errors: [ + "Unsupported content encoding: " + dce + ]}; + }) if(!valid) { - throw {message: "invalid payload", errors: validate.errors}; + throw {message: "invalid payload", errors: isValidAgainstSchema.errors}; } }; @@ -173,7 +196,7 @@ Spec03.prototype.getData = function() { }; Spec03.prototype.addExtension = function(key, value){ - if(!reserved.hasOwnProperty(key)){ + if(!RESERVED_ATTRIBUTES.hasOwnProperty(key)){ this.payload[key] = value; } else { throw {message: "Reserved attribute name: '" + key + "'"}; diff --git a/lib/utils/fun.js b/lib/utils/fun.js index 03abded..5534889 100644 --- a/lib/utils/fun.js +++ b/lib/utils/fun.js @@ -3,6 +3,11 @@ const isString = (v) => (typeof v) === "string"; const isObject = (v) => (typeof v) === "object"; const isDefined = (v) => v && (typeof v) != "undefined"; +const isStringOrThrow = (v, t) => + (isString(v) + ? true + : (() => {throw t;})()); + const isDefinedOrThrow = (v, t) => (isDefined(v) ? () => true @@ -15,11 +20,19 @@ const isStringOrObjectOrThrow = (v, t) => ? true : (() => {throw t;})()); +const equalsOrThrow = (v1, v2, t) => + (v1 === v2 + ? true + : (() => {throw t;})()); + module.exports = { isString, + isStringOrThrow, isObject, isDefined, isDefinedOrThrow, - isStringOrObjectOrThrow + isStringOrObjectOrThrow, + + equalsOrThrow }; diff --git a/test/fun_tests.js b/test/fun_tests.js new file mode 100644 index 0000000..aa4fdb9 --- /dev/null +++ b/test/fun_tests.js @@ -0,0 +1,32 @@ +const expect = require("chai").expect; +const fun = require("../lib/utils/fun.js"); + +describe("Functional approach", () => { + describe("isStringOrThrow", () => { + it("should throw when is not a string", () => { + expect(fun.isStringOrThrow.bind(fun, 3.6, {message: "works!"})) + .to + .throw("works!"); + }); + + it("should return true when is a string", () => { + expect(fun.isStringOrThrow("cool", {message: "not throws!"})) + .to + .equals(true); + }); + }); + + describe("equalsOrThrow", () => { + it("should throw when they are not equals", () => { + expect(fun.equalsOrThrow.bind(fun, "z", "a", {message: "works!"})) + .to + .throw("works!"); + }); + + it("should return true when they are equals", () => { + expect(fun.equalsOrThrow("z", "z", {message: "not throws!"})) + .to + .equals(true); + }); + }); +}); diff --git a/test/spec_0_3_tests.js b/test/spec_0_3_tests.js index a4fe982..51c3863 100644 --- a/test/spec_0_3_tests.js +++ b/test/spec_0_3_tests.js @@ -21,7 +21,6 @@ var cloudevent = .id(id) .source(source) .type(type) - .dataContentEncoding(dataContentEncoding) .dataContentType(dataContentType) .schemaurl(schemaurl) .subject(subject) @@ -50,7 +49,10 @@ describe("CloudEvents Spec v0.3", () => { describe("OPTIONAL Attributes", () => { it("Should have 'datacontentencoding'", () => { - expect(cloudevent.getDataContentEncoding()).to.equal(dataContentEncoding); + cloudevent.dataContentEncoding(dataContentEncoding); + expect(cloudevent.spec.payload.datacontentencoding) + .to.equal(dataContentEncoding); + delete cloudevent.spec.payload.datacontentencoding; }); it("Should have 'datacontenttype'", () => { @@ -162,8 +164,51 @@ describe("CloudEvents Spec v0.3", () => { }); describe("'datacontentencoding'", () => { - it("should throw an erro when 'data' is not a string", () => { + it("should throw an error when 'data' is not a string", () => { + cloudevent + .dataContentEncoding(dataContentEncoding); + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.data(data); + delete cloudevent.spec.payload.datacontentencoding; + }); + it("should throw an error when is a unsupported encoding" , () => { + cloudevent + .data("Y2xvdWRldmVudHMK") + .dataContentEncoding("binary"); + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + delete cloudevent.spec.payload.datacontentencoding; + cloudevent.data(data); + }); + + it("should accept when 'data' is a string", () => { + cloudevent + .data("Y2xvdWRldmVudHMK") + .dataContentEncoding("base64"); + expect(cloudevent.format()).to.have.property("datacontentencoding"); + delete cloudevent.spec.payload.datacontentencoding; + cloudevent.data(data); + }); + }); + + describe("'subject'", () => { + it("should throw an error when is an empty string", () => { + cloudevent.subject(""); + expect(cloudevent.format.bind(cloudevent)) + .to + .throw("invalid payload"); + cloudevent.subject(type); + }); + }); + + describe("'time'", () => { + it("must adhere to the format specified in RFC 3339", () => { + cloudevent.time(time); + expect(cloudevent.format()["time"]).to.equal(time.toISOString()); }); }); });