From 2e8cc9ab9b39dc2fc75611cff1f37c22bd38c2a4 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Thu, 10 Feb 2022 15:00:49 -0500 Subject: [PATCH 1/4] feat: precompile cloudevent schema This commit modifies the build pipleline so that the cloudevent schema is precompiled for runtime validation. This eliminates the need to compile the schema at runtime, improving both performance and security. Fixes: https://github.com/cloudevents/sdk-javascript/issues/423 Signed-off-by: Lance Ball --- .eslintrc | 1 + package-lock.json | 136 +++++++++++++++++++++++---- package.json | 12 ++- src/event/schemas.ts | 86 ----------------- src/event/spec.ts | 17 +--- src/schema/cloudevent.json | 128 +++++++++++++++++++++++++ src/schema/formats.js | 10 ++ src/schema/v1.js | 1 + test/integration/cloud_event_test.ts | 12 +-- test/integration/constants_test.ts | 58 ------------ test/integration/message_test.ts | 94 ------------------ test/integration/sdk_test.ts | 4 +- test/integration/spec_1_tests.ts | 2 +- 13 files changed, 275 insertions(+), 286 deletions(-) delete mode 100644 src/event/schemas.ts create mode 100644 src/schema/cloudevent.json create mode 100644 src/schema/formats.js create mode 100644 src/schema/v1.js diff --git a/.eslintrc b/.eslintrc index d515f85f..5b0543cb 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "plugins": [ "header" ], + "ignorePatterns": ["**/schema/*"], "rules": { "no-var": "error", "standard/no-callback-literal": "off", diff --git a/package-lock.json b/package-lock.json index 470c9a61..d24b8726 100644 --- a/package-lock.json +++ b/package-lock.json @@ -509,11 +509,29 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, @@ -651,15 +669,6 @@ "defer-to-connect": "^2.0.0" } }, - "@types/ajv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/ajv/-/ajv-1.0.0.tgz", - "integrity": "sha1-T7JEB0Ly9sMOf7B5e4OfxvaWaCo=", - "dev": true, - "requires": { - "ajv": "*" - } - }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -1162,16 +1171,39 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, + "ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dev": true, + "requires": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -2040,11 +2072,29 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, @@ -2370,10 +2420,28 @@ "micromatch": "^4.0.4" } }, + "fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -3381,10 +3449,19 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -5090,8 +5167,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require-main-filename": { "version": "2.0.0", @@ -5211,6 +5287,26 @@ "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "seed-random": { diff --git a/package.json b/package.json index dc259266..bd9f108f 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,15 @@ "main": "dist/index.js", "scripts": { "watch": "tsc --project tsconfig.json --watch", - "build": "tsc --project tsconfig.json && tsc --project tsconfig.browser.json && webpack", + "build:src": "tsc --project tsconfig.json", + "build:browser": "tsc --project tsconfig.browser.json && webpack", + "build:schema": "ajv compile -c ./src/schema/formats.js -s src/schema/cloudevent.json --strict-types false -o src/schema/v1.js", + "build": "npm run build:schema && npm run build:src && npm run build:browser", "lint": "npm run lint:md && npm run lint:js", "lint:js": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' cucumber.js", "lint:md": "remark .", "lint:fix": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' --fix", - "pretest": "npm run lint && npm run conformance", + "pretest": "npm run lint", "test": "mocha --require ts-node/register ./test/integration/**/*.ts", "conformance": "cucumber-js ./conformance/features/*-protocol-binding.feature -p default", "coverage": "nyc --reporter=lcov --reporter=text npm run test", @@ -106,13 +109,13 @@ }, "homepage": "https://github.com/cloudevents/sdk-javascript#readme", "dependencies": { - "ajv": "~6.12.3", + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", "util": "^0.12.4", "uuid": "~8.3.0" }, "devDependencies": { "@cucumber/cucumber": "^8.0.0-rc.1", - "@types/ajv": "^1.0.0", "@types/chai": "^4.2.11", "@types/cucumber": "^6.0.1", "@types/got": "^9.6.11", @@ -122,6 +125,7 @@ "@types/uuid": "^8.0.0", "@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/parser": "^4.29.0", + "ajv-cli": "^5.0.0", "axios": "^0.21.3", "chai": "~4.2.0", "eslint": "^7.32.0", diff --git a/src/event/schemas.ts b/src/event/schemas.ts deleted file mode 100644 index 9444a98f..00000000 --- a/src/event/schemas.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright 2021 The CloudEvents Authors - SPDX-License-Identifier: Apache-2.0 -*/ - -export const schemaV1 = { - $ref: "#/definitions/event", - definitions: { - specversion: { - type: "string", - minLength: 1, - const: "1.0", - }, - datacontenttype: { - type: "string", - }, - data: { - type: ["object", "string", "array", "number", "boolean", "null"], - }, - data_base64: { - type: "string", - }, - event: { - properties: { - specversion: { - $ref: "#/definitions/specversion", - }, - datacontenttype: { - $ref: "#/definitions/datacontenttype", - }, - data: { - $ref: "#/definitions/data", - }, - data_base64: { - $ref: "#/definitions/data_base64", - }, - id: { - $ref: "#/definitions/id", - }, - time: { - $ref: "#/definitions/time", - }, - dataschema: { - $ref: "#/definitions/dataschema", - }, - subject: { - $ref: "#/definitions/subject", - }, - type: { - $ref: "#/definitions/type", - }, - source: { - $ref: "#/definitions/source", - }, - }, - required: ["specversion", "id", "type", "source"], - type: "object", - }, - id: { - type: "string", - minLength: 1, - }, - time: { - format: "js-date-time", - type: "string", - }, - dataschema: { - type: "string", - format: "uri", - }, - subject: { - type: "string", - minLength: 1, - }, - type: { - type: "string", - minLength: 1, - }, - source: { - format: "uri-reference", - type: "string", - minLength: 1, - }, - }, - type: "object", -}; diff --git a/src/event/spec.ts b/src/event/spec.ts index e1f514fa..25def7df 100644 --- a/src/event/spec.ts +++ b/src/event/spec.ts @@ -3,28 +3,17 @@ SPDX-License-Identifier: Apache-2.0 */ -import Ajv, { Options } from "ajv"; import { ValidationError } from "./validation"; import { CloudEventV1 } from "./interfaces"; -import { schemaV1 } from "./schemas"; import { Version } from "./cloudevent"; +import validate from "../schema/v1"; -const ajv = new Ajv({ extendRefs: true } as Options); - -// handle date-time format specially because a user could pass -// Date().toString(), which is not spec compliant date-time format -ajv.addFormat("js-date-time", function (dateTimeString) { - const date = new Date(Date.parse(dateTimeString)); - return date.toString() !== "Invalid Date"; -}); - -const isValidAgainstSchemaV1 = ajv.compile(schemaV1); export function validateCloudEvent(event: CloudEventV1): boolean { if (event.specversion === Version.V1) { - if (!isValidAgainstSchemaV1(event)) { - throw new ValidationError("invalid payload", isValidAgainstSchemaV1.errors); + if (!validate(event)) { + throw new ValidationError("invalid payload", (validate as any).errors); } } else { return false; diff --git a/src/schema/cloudevent.json b/src/schema/cloudevent.json new file mode 100644 index 00000000..b9d55503 --- /dev/null +++ b/src/schema/cloudevent.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "CloudEvents Specification JSON Schema", + "type": "object", + "properties": { + "id": { + "description": "Identifies the event.", + "$ref": "#/definitions/iddef", + "examples": [ + "A234-1234-1234" + ] + }, + "source": { + "description": "Identifies the context in which an event happened.", + "$ref": "#/definitions/sourcedef", + "examples" : [ + "https://github.com/cloudevents", + "mailto:cncf-wg-serverless@lists.cncf.io", + "urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66", + "cloudevents/spec/pull/123", + "/sensors/tn-1234567/alerts", + "1-555-123-4567" + ] + }, + "specversion": { + "description": "The version of the CloudEvents specification which the event uses.", + "$ref": "#/definitions/specversiondef", + "examples": [ + "1.0" + ] + }, + "type": { + "description": "Describes the type of event related to the originating occurrence.", + "$ref": "#/definitions/typedef", + "examples" : [ + "com.github.pull_request.opened", + "com.example.object.deleted.v2" + ] + }, + "datacontenttype": { + "description": "Content type of the data value. Must adhere to RFC 2046 format.", + "$ref": "#/definitions/datacontenttypedef", + "examples": [ + "text/xml", + "application/json", + "image/png", + "multipart/form-data" + ] + }, + "dataschema": { + "description": "Identifies the schema that data adheres to.", + "$ref": "#/definitions/dataschemadef" + }, + "subject": { + "description": "Describes the subject of the event in the context of the event producer (identified by source).", + "$ref": "#/definitions/subjectdef", + "examples": [ + "mynewfile.jpg" + ] + }, + "time": { + "description": "Timestamp of when the occurrence happened. Must adhere to RFC 3339.", + "$ref": "#/definitions/timedef", + "examples": [ + "2018-04-05T17:31:00Z" + ] + }, + "data": { + "description": "The event payload.", + "$ref": "#/definitions/datadef", + "examples": [ + "" + ] + }, + "data_base64": { + "description": "Base64 encoded event payload. Must adhere to RFC4648.", + "$ref": "#/definitions/data_base64def", + "examples": [ + "Zm9vYg==" + ] + } + }, + "required": ["id", "source", "specversion", "type"], + "definitions": { + "iddef": { + "type": "string", + "minLength": 1 + }, + "sourcedef": { + "type": "string", + "format": "uri-reference", + "minLength": 1 + }, + "specversiondef": { + "type": "string", + "minLength": 1 + }, + "typedef": { + "type": "string", + "minLength": 1 + }, + "datacontenttypedef": { + "type": ["string", "null"], + "minLength": 1 + }, + "dataschemadef": { + "type": ["string", "null"], + "format": "uri", + "minLength": 1 + }, + "subjectdef": { + "type": ["string", "null"], + "minLength": 1 + }, + "timedef": { + "type": ["string", "null"], + "format": "date-time", + "minLength": 1 + }, + "datadef": { + "type": ["object", "string", "number", "array", "boolean", "null"] + }, + "data_base64def": { + "type": ["string", "null"], + "contentEncoding": "base64" + } + } +} diff --git a/src/schema/formats.js b/src/schema/formats.js new file mode 100644 index 00000000..f19ee1fa --- /dev/null +++ b/src/schema/formats.js @@ -0,0 +1,10 @@ +/* + Copyright 2021 The CloudEvents Authors + SPDX-License-Identifier: Apache-2.0 +*/ + +function formats(ajv) { + require("ajv-formats")(ajv); +} + +module.exports = formats; diff --git a/src/schema/v1.js b/src/schema/v1.js new file mode 100644 index 00000000..d0354233 --- /dev/null +++ b/src/schema/v1.js @@ -0,0 +1 @@ +"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"$schema":"http://json-schema.org/draft-07/schema#","description":"CloudEvents Specification JSON Schema","type":"object","properties":{"id":{"description":"Identifies the event.","$ref":"#/definitions/iddef","examples":["A234-1234-1234"]},"source":{"description":"Identifies the context in which an event happened.","$ref":"#/definitions/sourcedef","examples":["https://github.com/cloudevents","mailto:cncf-wg-serverless@lists.cncf.io","urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66","cloudevents/spec/pull/123","/sensors/tn-1234567/alerts","1-555-123-4567"]},"specversion":{"description":"The version of the CloudEvents specification which the event uses.","$ref":"#/definitions/specversiondef","examples":["1.0"]},"type":{"description":"Describes the type of event related to the originating occurrence.","$ref":"#/definitions/typedef","examples":["com.github.pull_request.opened","com.example.object.deleted.v2"]},"datacontenttype":{"description":"Content type of the data value. Must adhere to RFC 2046 format.","$ref":"#/definitions/datacontenttypedef","examples":["text/xml","application/json","image/png","multipart/form-data"]},"dataschema":{"description":"Identifies the schema that data adheres to.","$ref":"#/definitions/dataschemadef"},"subject":{"description":"Describes the subject of the event in the context of the event producer (identified by source).","$ref":"#/definitions/subjectdef","examples":["mynewfile.jpg"]},"time":{"description":"Timestamp of when the occurrence happened. Must adhere to RFC 3339.","$ref":"#/definitions/timedef","examples":["2018-04-05T17:31:00Z"]},"data":{"description":"The event payload.","$ref":"#/definitions/datadef","examples":[""]},"data_base64":{"description":"Base64 encoded event payload. Must adhere to RFC4648.","$ref":"#/definitions/data_base64def","examples":["Zm9vYg=="]}},"required":["id","source","specversion","type"],"definitions":{"iddef":{"type":"string","minLength":1},"sourcedef":{"type":"string","format":"uri-reference","minLength":1},"specversiondef":{"type":"string","minLength":1},"typedef":{"type":"string","minLength":1},"datacontenttypedef":{"type":["string","null"],"minLength":1},"dataschemadef":{"type":["string","null"],"format":"uri","minLength":1},"subjectdef":{"type":["string","null"],"minLength":1},"timedef":{"type":["string","null"],"format":"date-time","minLength":1},"datadef":{"type":["object","string","number","array","boolean","null"]},"data_base64def":{"type":["string","null"],"contentEncoding":"base64"}}};const schema23 = {"type":"string","minLength":1};const schema24 = {"type":"string","format":"uri-reference","minLength":1};const schema25 = {"type":"string","minLength":1};const schema26 = {"type":"string","minLength":1};const schema27 = {"type":["string","null"],"minLength":1};const schema28 = {"type":["string","null"],"format":"uri","minLength":1};const schema29 = {"type":["string","null"],"minLength":1};const schema30 = {"type":["string","null"],"format":"date-time","minLength":1};const schema31 = {"type":["object","string","number","array","boolean","null"]};const schema32 = {"type":["string","null"],"contentEncoding":"base64"};const func8 = require("ajv/dist/runtime/ucs2length").default;const formats0 = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;const formats2 = require("ajv-formats/dist/formats").fullFormats.uri;const formats4 = require("ajv-formats/dist/formats").fullFormats["date-time"];function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if(((((data.id === undefined) && (missing0 = "id")) || ((data.source === undefined) && (missing0 = "source"))) || ((data.specversion === undefined) && (missing0 = "specversion"))) || ((data.type === undefined) && (missing0 = "type"))){validate20.errors = [{instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: missing0},message:"must have required property '"+missing0+"'"}];return false;}else {if(data.id !== undefined){let data0 = data.id;const _errs1 = errors;const _errs2 = errors;if(errors === _errs2){if(typeof data0 === "string"){if(func8(data0) < 1){validate20.errors = [{instancePath:instancePath+"/id",schemaPath:"#/definitions/iddef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/id",schemaPath:"#/definitions/iddef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs1 === errors;}else {var valid0 = true;}if(valid0){if(data.source !== undefined){let data1 = data.source;const _errs4 = errors;const _errs5 = errors;if(errors === _errs5){if(errors === _errs5){if(typeof data1 === "string"){if(func8(data1) < 1){validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats0.test(data1))){validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/format",keyword:"format",params:{format: "uri-reference"},message:"must match format \""+"uri-reference"+"\""}];return false;}}}else {validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}}var valid0 = _errs4 === errors;}else {var valid0 = true;}if(valid0){if(data.specversion !== undefined){let data2 = data.specversion;const _errs7 = errors;const _errs8 = errors;if(errors === _errs8){if(typeof data2 === "string"){if(func8(data2) < 1){validate20.errors = [{instancePath:instancePath+"/specversion",schemaPath:"#/definitions/specversiondef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/specversion",schemaPath:"#/definitions/specversiondef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs7 === errors;}else {var valid0 = true;}if(valid0){if(data.type !== undefined){let data3 = data.type;const _errs10 = errors;const _errs11 = errors;if(errors === _errs11){if(typeof data3 === "string"){if(func8(data3) < 1){validate20.errors = [{instancePath:instancePath+"/type",schemaPath:"#/definitions/typedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/type",schemaPath:"#/definitions/typedef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs10 === errors;}else {var valid0 = true;}if(valid0){if(data.datacontenttype !== undefined){let data4 = data.datacontenttype;const _errs13 = errors;const _errs14 = errors;if((typeof data4 !== "string") && (data4 !== null)){validate20.errors = [{instancePath:instancePath+"/datacontenttype",schemaPath:"#/definitions/datacontenttypedef/type",keyword:"type",params:{type: schema27.type},message:"must be string,null"}];return false;}if(errors === _errs14){if(typeof data4 === "string"){if(func8(data4) < 1){validate20.errors = [{instancePath:instancePath+"/datacontenttype",schemaPath:"#/definitions/datacontenttypedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}}var valid0 = _errs13 === errors;}else {var valid0 = true;}if(valid0){if(data.dataschema !== undefined){let data5 = data.dataschema;const _errs16 = errors;const _errs17 = errors;if((typeof data5 !== "string") && (data5 !== null)){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/type",keyword:"type",params:{type: schema28.type},message:"must be string,null"}];return false;}if(errors === _errs17){if(errors === _errs17){if(typeof data5 === "string"){if(func8(data5) < 1){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats2(data5))){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/format",keyword:"format",params:{format: "uri"},message:"must match format \""+"uri"+"\""}];return false;}}}}}var valid0 = _errs16 === errors;}else {var valid0 = true;}if(valid0){if(data.subject !== undefined){let data6 = data.subject;const _errs19 = errors;const _errs20 = errors;if((typeof data6 !== "string") && (data6 !== null)){validate20.errors = [{instancePath:instancePath+"/subject",schemaPath:"#/definitions/subjectdef/type",keyword:"type",params:{type: schema29.type},message:"must be string,null"}];return false;}if(errors === _errs20){if(typeof data6 === "string"){if(func8(data6) < 1){validate20.errors = [{instancePath:instancePath+"/subject",schemaPath:"#/definitions/subjectdef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}}var valid0 = _errs19 === errors;}else {var valid0 = true;}if(valid0){if(data.time !== undefined){let data7 = data.time;const _errs22 = errors;const _errs23 = errors;if((typeof data7 !== "string") && (data7 !== null)){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/type",keyword:"type",params:{type: schema30.type},message:"must be string,null"}];return false;}if(errors === _errs23){if(errors === _errs23){if(typeof data7 === "string"){if(func8(data7) < 1){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats4.validate(data7))){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}];return false;}}}}}var valid0 = _errs22 === errors;}else {var valid0 = true;}if(valid0){if(data.data !== undefined){let data8 = data.data;const _errs25 = errors;if((((typeof data8 != "object") && (typeof data8 !== "string")) && (!((typeof data8 == "number") && (isFinite(data8))))) && (typeof data8 !== "boolean")){validate20.errors = [{instancePath:instancePath+"/data",schemaPath:"#/definitions/datadef/type",keyword:"type",params:{type: schema31.type},message:"must be object,string,number,array,boolean,null"}];return false;}var valid0 = _errs25 === errors;}else {var valid0 = true;}if(valid0){if(data.data_base64 !== undefined){let data9 = data.data_base64;const _errs28 = errors;if((typeof data9 !== "string") && (data9 !== null)){validate20.errors = [{instancePath:instancePath+"/data_base64",schemaPath:"#/definitions/data_base64def/type",keyword:"type",params:{type: schema32.type},message:"must be string,null"}];return false;}var valid0 = _errs28 === errors;}else {var valid0 = true;}}}}}}}}}}}}else {validate20.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate20.errors = vErrors;return errors === 0;} \ No newline at end of file diff --git a/test/integration/cloud_event_test.ts b/test/integration/cloud_event_test.ts index ba40ce56..f7c95c07 100644 --- a/test/integration/cloud_event_test.ts +++ b/test/integration/cloud_event_test.ts @@ -9,7 +9,6 @@ import fs from "fs"; import { expect } from "chai"; import { CloudEvent, ValidationError, Version } from "../../src"; import { asBase64 } from "../../src/event/validation"; -import { ErrorObject } from "schema-utils/declarations/validate"; const type = "org.cncf.cloudevents.example"; const source = "http://unit.test"; @@ -225,13 +224,12 @@ describe("A 1.0 CloudEvent", () => { type: "my.event.type", source: "", }); - } catch (err) { + } catch (err: any) { expect(err).to.be.instanceOf(ValidationError); - const e = err as unknown as ValidationError; - const errors = e.errors as ErrorObject[]; - expect(e.message).to.include("invalid payload"); - expect(errors[0].dataPath).to.equal(".source"); - expect(errors[0].keyword).to.equal("minLength"); + const error = err.errors[0] as any; + expect(err.message).to.include("invalid payload"); + expect(error.instancePath).to.equal("/source"); + expect(error.keyword).to.equal("minLength"); } }); }); diff --git a/test/integration/constants_test.ts b/test/integration/constants_test.ts index e98ec871..a59d93ed 100644 --- a/test/integration/constants_test.ts +++ b/test/integration/constants_test.ts @@ -36,64 +36,6 @@ describe("Constants exposed by top level exports", () => { `${CONSTANTS.MIME_CE_JSON}; charset=${CONSTANTS.CHARSET_DEFAULT}`, ); }); - describe("V0.3 binary headers constants", () => { - it("Provides a TYPE header", () => { - expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type"); - }); - it("Provides a SPEC_VERSION header", () => { - expect(CONSTANTS.CE_HEADERS.SPEC_VERSION).to.equal("ce-specversion"); - }); - it("Provides a SOURCE header", () => { - expect(CONSTANTS.CE_HEADERS.SOURCE).to.equal("ce-source"); - }); - it("Provides an ID header", () => { - expect(CONSTANTS.CE_HEADERS.ID).to.equal("ce-id"); - }); - it("Provides a TIME header", () => { - expect(CONSTANTS.CE_HEADERS.TIME).to.equal("ce-time"); - }); - it("Provides a SCHEMA_URL header", () => { - expect(CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL).to.equal("ce-schemaurl"); - }); - it("Provides a CONTENT_ENCODING header", () => { - expect(CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING).to.equal("ce-datacontentencoding"); - }); - it("Provides a SUBJECT header", () => { - expect(CONSTANTS.CE_HEADERS.SUBJECT).to.equal("ce-subject"); - }); - it("Provides an EXTENSIONS_PREFIX constant", () => { - expect(CONSTANTS.EXTENSIONS_PREFIX).to.equal("ce-"); - }); - }); - describe("V0.3 structured attributes constants", () => { - it("Provides a TYPE attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.TYPE).to.equal("type"); - }); - it("Provides a SPEC_VERSION attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION).to.equal("specversion"); - }); - it("Provides a SOURCE attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.SOURCE).to.equal("source"); - }); - it("Provides an ID attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.ID).to.equal("id"); - }); - it("Provides a TIME attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.TIME).to.equal("time"); - }); - it("Provides a SCHEMA_URL attribute", () => { - expect(CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL).to.equal("schemaurl"); - }); - it("Provides a CONTENT_ENCODING attribute", () => { - expect(CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING).to.equal("datacontentencoding"); - }); - it("Provides a SUBJECT attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.SUBJECT).to.equal("subject"); - }); - it("Provides a DATA attribute", () => { - expect(CONSTANTS.CE_ATTRIBUTES.DATA).to.equal("data"); - }); - }); describe("V01 binary headers constants", () => { it("Provides a TYPE header", () => { expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type"); diff --git a/test/integration/message_test.ts b/test/integration/message_test.ts index f4f8e210..c1ebd5fb 100644 --- a/test/integration/message_test.ts +++ b/test/integration/message_test.ts @@ -23,9 +23,6 @@ const data = { foo: "bar", }; -// Attributes for v03 events -const schemaurl = "https://cloudevents.io/schema.json"; - const ext1Name = "extension1"; const ext1Value = "foobar"; const ext2Name = "extension2"; @@ -296,95 +293,4 @@ describe("HTTP transport", () => { expect(eventDeserialized.data_base64).to.equal(data_base64); }); }); - - describe("Specification version V03", () => { - const fixture = new CloudEvent({ - specversion: Version.V03, - id, - type, - source, - datacontenttype, - subject, - time, - schemaurl, - data, - [ext1Name]: ext1Value, - [ext2Name]: ext2Value, - }); - - it("Binary Messages can be created from a CloudEvent", () => { - const message: Message = HTTP.binary(fixture); - expect(message.body).to.equal(JSON.stringify(data)); - // validate all headers - expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); - expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03); - expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id); - expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type); - expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source); - expect(message.headers[CONSTANTS.CE_HEADERS.SUBJECT]).to.equal(subject); - expect(message.headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(fixture.time); - expect(message.headers[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]).to.equal(schemaurl); - expect(message.headers[`ce-${ext1Name}`]).to.equal(ext1Value); - expect(message.headers[`ce-${ext2Name}`]).to.equal(ext2Value); - }); - - it("Structured Messages can be created from a CloudEvent", () => { - const message: Message = HTTP.structured(fixture); - expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); - // Parse the message body as JSON, then validate the attributes - const body = JSON.parse(message.body as string); - expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03); - expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); - expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); - expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); - expect(body[CONSTANTS.CE_ATTRIBUTES.SUBJECT]).to.equal(subject); - expect(body[CONSTANTS.CE_ATTRIBUTES.TIME]).to.equal(fixture.time); - expect(body[CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL]).to.equal(schemaurl); - expect(body[ext1Name]).to.equal(ext1Value); - expect(body[ext2Name]).to.equal(ext2Value); - }); - - it("A CloudEvent can be converted from a binary Message", () => { - const message = HTTP.binary(fixture); - const event = HTTP.toEvent(message); - expect(event).to.deep.equal(fixture); - }); - - it("A CloudEvent can be converted from a structured Message", () => { - const message = HTTP.structured(fixture); - const event = HTTP.toEvent(message); - expect(event).to.deep.equal(fixture); - }); - - it("Converts binary data to base64 when serializing structured messages", () => { - const event = fixture.cloneWith({ data: imageData, datacontenttype: "image/png" }); - expect(event.data).to.equal(imageData); - const message = HTTP.structured(event); - const messageBody = JSON.parse(message.body as string); - expect(messageBody.data_base64).to.equal(image_base64); - }); - - it("Converts base64 encoded data to binary when deserializing structured messages", () => { - // Creating an event with binary data automatically produces base64 encoded data - // which is then set as the 'data' attribute on the message body - const message = HTTP.structured(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); - const eventDeserialized = HTTP.toEvent(message) as CloudEvent; - expect(eventDeserialized.data).to.deep.equal(imageData); - expect(eventDeserialized.data_base64).to.equal(image_base64); - }); - - it("Converts base64 encoded data to binary when deserializing binary messages", () => { - const message = HTTP.binary(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); - const eventDeserialized = HTTP.toEvent(message) as CloudEvent; - expect(eventDeserialized.data).to.deep.equal(imageData); - expect(eventDeserialized.data_base64).to.equal(image_base64); - }); - - it("Keeps binary data binary when serializing binary messages", () => { - const event = fixture.cloneWith({ data: dataBinary }); - expect(event.data).to.equal(dataBinary); - const message = HTTP.binary(event); - expect(message.body).to.equal(dataBinary); - }); - }); }); diff --git a/test/integration/sdk_test.ts b/test/integration/sdk_test.ts index 37dc4fd9..04483c07 100644 --- a/test/integration/sdk_test.ts +++ b/test/integration/sdk_test.ts @@ -21,12 +21,12 @@ describe("The SDK Requirements", () => { }); describe("v0.3", () => { - it("should create an event using the right spec version", () => { + it("should create an (invalid) event using the right spec version", () => { expect( new CloudEvent({ ...fixture, specversion: Version.V03, - }).specversion, + }, false).specversion, ).to.equal(Version.V03); }); }); diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index e83decd0..e2bbd625 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -150,7 +150,7 @@ describe("CloudEvents Spec v1.0", () => { describe("'time'", () => { it("must adhere to the format specified in RFC 3339", () => { const d = new Date(); - cloudevent = cloudevent.cloneWith({ time: d.toString() }); + cloudevent = cloudevent.cloneWith({ time: d.toString() }, false); // ensure that we always get back the same thing we passed in expect(cloudevent.time).to.equal(d.toString()); // ensure that when stringified, the timestamp is in RFC3339 format From 9e9c0f0b23faccb960382e58461840e531f6be40 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Thu, 10 Feb 2022 15:05:34 -0500 Subject: [PATCH 2/4] fixup: ajv-formats in dev deps Signed-off-by: Lance Ball --- package-lock.json | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d24b8726..fc1726b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1200,6 +1200,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "requires": { "ajv": "^8.0.0" } diff --git a/package.json b/package.json index bd9f108f..0b381ed7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint:js": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' cucumber.js", "lint:md": "remark .", "lint:fix": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' --fix", - "pretest": "npm run lint", + "pretest": "npm run lint && npm run conformance", "test": "mocha --require ts-node/register ./test/integration/**/*.ts", "conformance": "cucumber-js ./conformance/features/*-protocol-binding.feature -p default", "coverage": "nyc --reporter=lcov --reporter=text npm run test", @@ -110,7 +110,6 @@ "homepage": "https://github.com/cloudevents/sdk-javascript#readme", "dependencies": { "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", "util": "^0.12.4", "uuid": "~8.3.0" }, @@ -126,6 +125,7 @@ "@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/parser": "^4.29.0", "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "axios": "^0.21.3", "chai": "~4.2.0", "eslint": "^7.32.0", From e5b90e7084b9d2eac0dc75dc0c13e55b32400352 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Mon, 14 Feb 2022 15:13:30 -0500 Subject: [PATCH 3/4] fixup: revert v0.3 changes Signed-off-by: Lance Ball --- test/integration/constants_test.ts | 58 ++++++++++++++++++ test/integration/message_test.ts | 94 ++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/test/integration/constants_test.ts b/test/integration/constants_test.ts index a59d93ed..e98ec871 100644 --- a/test/integration/constants_test.ts +++ b/test/integration/constants_test.ts @@ -36,6 +36,64 @@ describe("Constants exposed by top level exports", () => { `${CONSTANTS.MIME_CE_JSON}; charset=${CONSTANTS.CHARSET_DEFAULT}`, ); }); + describe("V0.3 binary headers constants", () => { + it("Provides a TYPE header", () => { + expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type"); + }); + it("Provides a SPEC_VERSION header", () => { + expect(CONSTANTS.CE_HEADERS.SPEC_VERSION).to.equal("ce-specversion"); + }); + it("Provides a SOURCE header", () => { + expect(CONSTANTS.CE_HEADERS.SOURCE).to.equal("ce-source"); + }); + it("Provides an ID header", () => { + expect(CONSTANTS.CE_HEADERS.ID).to.equal("ce-id"); + }); + it("Provides a TIME header", () => { + expect(CONSTANTS.CE_HEADERS.TIME).to.equal("ce-time"); + }); + it("Provides a SCHEMA_URL header", () => { + expect(CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL).to.equal("ce-schemaurl"); + }); + it("Provides a CONTENT_ENCODING header", () => { + expect(CONSTANTS.BINARY_HEADERS_03.CONTENT_ENCODING).to.equal("ce-datacontentencoding"); + }); + it("Provides a SUBJECT header", () => { + expect(CONSTANTS.CE_HEADERS.SUBJECT).to.equal("ce-subject"); + }); + it("Provides an EXTENSIONS_PREFIX constant", () => { + expect(CONSTANTS.EXTENSIONS_PREFIX).to.equal("ce-"); + }); + }); + describe("V0.3 structured attributes constants", () => { + it("Provides a TYPE attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.TYPE).to.equal("type"); + }); + it("Provides a SPEC_VERSION attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION).to.equal("specversion"); + }); + it("Provides a SOURCE attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.SOURCE).to.equal("source"); + }); + it("Provides an ID attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.ID).to.equal("id"); + }); + it("Provides a TIME attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.TIME).to.equal("time"); + }); + it("Provides a SCHEMA_URL attribute", () => { + expect(CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL).to.equal("schemaurl"); + }); + it("Provides a CONTENT_ENCODING attribute", () => { + expect(CONSTANTS.STRUCTURED_ATTRS_03.CONTENT_ENCODING).to.equal("datacontentencoding"); + }); + it("Provides a SUBJECT attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.SUBJECT).to.equal("subject"); + }); + it("Provides a DATA attribute", () => { + expect(CONSTANTS.CE_ATTRIBUTES.DATA).to.equal("data"); + }); + }); describe("V01 binary headers constants", () => { it("Provides a TYPE header", () => { expect(CONSTANTS.CE_HEADERS.TYPE).to.equal("ce-type"); diff --git a/test/integration/message_test.ts b/test/integration/message_test.ts index c1ebd5fb..f4f8e210 100644 --- a/test/integration/message_test.ts +++ b/test/integration/message_test.ts @@ -23,6 +23,9 @@ const data = { foo: "bar", }; +// Attributes for v03 events +const schemaurl = "https://cloudevents.io/schema.json"; + const ext1Name = "extension1"; const ext1Value = "foobar"; const ext2Name = "extension2"; @@ -293,4 +296,95 @@ describe("HTTP transport", () => { expect(eventDeserialized.data_base64).to.equal(data_base64); }); }); + + describe("Specification version V03", () => { + const fixture = new CloudEvent({ + specversion: Version.V03, + id, + type, + source, + datacontenttype, + subject, + time, + schemaurl, + data, + [ext1Name]: ext1Value, + [ext2Name]: ext2Value, + }); + + it("Binary Messages can be created from a CloudEvent", () => { + const message: Message = HTTP.binary(fixture); + expect(message.body).to.equal(JSON.stringify(data)); + // validate all headers + expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype); + expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03); + expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id); + expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type); + expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source); + expect(message.headers[CONSTANTS.CE_HEADERS.SUBJECT]).to.equal(subject); + expect(message.headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(fixture.time); + expect(message.headers[CONSTANTS.BINARY_HEADERS_03.SCHEMA_URL]).to.equal(schemaurl); + expect(message.headers[`ce-${ext1Name}`]).to.equal(ext1Value); + expect(message.headers[`ce-${ext2Name}`]).to.equal(ext2Value); + }); + + it("Structured Messages can be created from a CloudEvent", () => { + const message: Message = HTTP.structured(fixture); + expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE); + // Parse the message body as JSON, then validate the attributes + const body = JSON.parse(message.body as string); + expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03); + expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id); + expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type); + expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source); + expect(body[CONSTANTS.CE_ATTRIBUTES.SUBJECT]).to.equal(subject); + expect(body[CONSTANTS.CE_ATTRIBUTES.TIME]).to.equal(fixture.time); + expect(body[CONSTANTS.STRUCTURED_ATTRS_03.SCHEMA_URL]).to.equal(schemaurl); + expect(body[ext1Name]).to.equal(ext1Value); + expect(body[ext2Name]).to.equal(ext2Value); + }); + + it("A CloudEvent can be converted from a binary Message", () => { + const message = HTTP.binary(fixture); + const event = HTTP.toEvent(message); + expect(event).to.deep.equal(fixture); + }); + + it("A CloudEvent can be converted from a structured Message", () => { + const message = HTTP.structured(fixture); + const event = HTTP.toEvent(message); + expect(event).to.deep.equal(fixture); + }); + + it("Converts binary data to base64 when serializing structured messages", () => { + const event = fixture.cloneWith({ data: imageData, datacontenttype: "image/png" }); + expect(event.data).to.equal(imageData); + const message = HTTP.structured(event); + const messageBody = JSON.parse(message.body as string); + expect(messageBody.data_base64).to.equal(image_base64); + }); + + it("Converts base64 encoded data to binary when deserializing structured messages", () => { + // Creating an event with binary data automatically produces base64 encoded data + // which is then set as the 'data' attribute on the message body + const message = HTTP.structured(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); + const eventDeserialized = HTTP.toEvent(message) as CloudEvent; + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Converts base64 encoded data to binary when deserializing binary messages", () => { + const message = HTTP.binary(fixture.cloneWith({ data: imageData, datacontenttype: "image/png" })); + const eventDeserialized = HTTP.toEvent(message) as CloudEvent; + expect(eventDeserialized.data).to.deep.equal(imageData); + expect(eventDeserialized.data_base64).to.equal(image_base64); + }); + + it("Keeps binary data binary when serializing binary messages", () => { + const event = fixture.cloneWith({ data: dataBinary }); + expect(event.data).to.equal(dataBinary); + const message = HTTP.binary(event); + expect(message.body).to.equal(dataBinary); + }); + }); }); From e196efcf8bad43b9f3c933f4129633f8cda1074b Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Mon, 14 Feb 2022 15:21:43 -0500 Subject: [PATCH 4/4] fixup: make src/schema/v1.js transient Signed-off-by: Lance Ball --- .gitignore | 1 + src/schema/v1.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/schema/v1.js diff --git a/.gitignore b/.gitignore index 1a0ce74e..90afecec 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ index.js /bundles /dist /docs +src/schema/v1.js # Runtime data pids diff --git a/src/schema/v1.js b/src/schema/v1.js deleted file mode 100644 index d0354233..00000000 --- a/src/schema/v1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"$schema":"http://json-schema.org/draft-07/schema#","description":"CloudEvents Specification JSON Schema","type":"object","properties":{"id":{"description":"Identifies the event.","$ref":"#/definitions/iddef","examples":["A234-1234-1234"]},"source":{"description":"Identifies the context in which an event happened.","$ref":"#/definitions/sourcedef","examples":["https://github.com/cloudevents","mailto:cncf-wg-serverless@lists.cncf.io","urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66","cloudevents/spec/pull/123","/sensors/tn-1234567/alerts","1-555-123-4567"]},"specversion":{"description":"The version of the CloudEvents specification which the event uses.","$ref":"#/definitions/specversiondef","examples":["1.0"]},"type":{"description":"Describes the type of event related to the originating occurrence.","$ref":"#/definitions/typedef","examples":["com.github.pull_request.opened","com.example.object.deleted.v2"]},"datacontenttype":{"description":"Content type of the data value. Must adhere to RFC 2046 format.","$ref":"#/definitions/datacontenttypedef","examples":["text/xml","application/json","image/png","multipart/form-data"]},"dataschema":{"description":"Identifies the schema that data adheres to.","$ref":"#/definitions/dataschemadef"},"subject":{"description":"Describes the subject of the event in the context of the event producer (identified by source).","$ref":"#/definitions/subjectdef","examples":["mynewfile.jpg"]},"time":{"description":"Timestamp of when the occurrence happened. Must adhere to RFC 3339.","$ref":"#/definitions/timedef","examples":["2018-04-05T17:31:00Z"]},"data":{"description":"The event payload.","$ref":"#/definitions/datadef","examples":[""]},"data_base64":{"description":"Base64 encoded event payload. Must adhere to RFC4648.","$ref":"#/definitions/data_base64def","examples":["Zm9vYg=="]}},"required":["id","source","specversion","type"],"definitions":{"iddef":{"type":"string","minLength":1},"sourcedef":{"type":"string","format":"uri-reference","minLength":1},"specversiondef":{"type":"string","minLength":1},"typedef":{"type":"string","minLength":1},"datacontenttypedef":{"type":["string","null"],"minLength":1},"dataschemadef":{"type":["string","null"],"format":"uri","minLength":1},"subjectdef":{"type":["string","null"],"minLength":1},"timedef":{"type":["string","null"],"format":"date-time","minLength":1},"datadef":{"type":["object","string","number","array","boolean","null"]},"data_base64def":{"type":["string","null"],"contentEncoding":"base64"}}};const schema23 = {"type":"string","minLength":1};const schema24 = {"type":"string","format":"uri-reference","minLength":1};const schema25 = {"type":"string","minLength":1};const schema26 = {"type":"string","minLength":1};const schema27 = {"type":["string","null"],"minLength":1};const schema28 = {"type":["string","null"],"format":"uri","minLength":1};const schema29 = {"type":["string","null"],"minLength":1};const schema30 = {"type":["string","null"],"format":"date-time","minLength":1};const schema31 = {"type":["object","string","number","array","boolean","null"]};const schema32 = {"type":["string","null"],"contentEncoding":"base64"};const func8 = require("ajv/dist/runtime/ucs2length").default;const formats0 = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;const formats2 = require("ajv-formats/dist/formats").fullFormats.uri;const formats4 = require("ajv-formats/dist/formats").fullFormats["date-time"];function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){let missing0;if(((((data.id === undefined) && (missing0 = "id")) || ((data.source === undefined) && (missing0 = "source"))) || ((data.specversion === undefined) && (missing0 = "specversion"))) || ((data.type === undefined) && (missing0 = "type"))){validate20.errors = [{instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: missing0},message:"must have required property '"+missing0+"'"}];return false;}else {if(data.id !== undefined){let data0 = data.id;const _errs1 = errors;const _errs2 = errors;if(errors === _errs2){if(typeof data0 === "string"){if(func8(data0) < 1){validate20.errors = [{instancePath:instancePath+"/id",schemaPath:"#/definitions/iddef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/id",schemaPath:"#/definitions/iddef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs1 === errors;}else {var valid0 = true;}if(valid0){if(data.source !== undefined){let data1 = data.source;const _errs4 = errors;const _errs5 = errors;if(errors === _errs5){if(errors === _errs5){if(typeof data1 === "string"){if(func8(data1) < 1){validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats0.test(data1))){validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/format",keyword:"format",params:{format: "uri-reference"},message:"must match format \""+"uri-reference"+"\""}];return false;}}}else {validate20.errors = [{instancePath:instancePath+"/source",schemaPath:"#/definitions/sourcedef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}}var valid0 = _errs4 === errors;}else {var valid0 = true;}if(valid0){if(data.specversion !== undefined){let data2 = data.specversion;const _errs7 = errors;const _errs8 = errors;if(errors === _errs8){if(typeof data2 === "string"){if(func8(data2) < 1){validate20.errors = [{instancePath:instancePath+"/specversion",schemaPath:"#/definitions/specversiondef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/specversion",schemaPath:"#/definitions/specversiondef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs7 === errors;}else {var valid0 = true;}if(valid0){if(data.type !== undefined){let data3 = data.type;const _errs10 = errors;const _errs11 = errors;if(errors === _errs11){if(typeof data3 === "string"){if(func8(data3) < 1){validate20.errors = [{instancePath:instancePath+"/type",schemaPath:"#/definitions/typedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}else {validate20.errors = [{instancePath:instancePath+"/type",schemaPath:"#/definitions/typedef/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}var valid0 = _errs10 === errors;}else {var valid0 = true;}if(valid0){if(data.datacontenttype !== undefined){let data4 = data.datacontenttype;const _errs13 = errors;const _errs14 = errors;if((typeof data4 !== "string") && (data4 !== null)){validate20.errors = [{instancePath:instancePath+"/datacontenttype",schemaPath:"#/definitions/datacontenttypedef/type",keyword:"type",params:{type: schema27.type},message:"must be string,null"}];return false;}if(errors === _errs14){if(typeof data4 === "string"){if(func8(data4) < 1){validate20.errors = [{instancePath:instancePath+"/datacontenttype",schemaPath:"#/definitions/datacontenttypedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}}var valid0 = _errs13 === errors;}else {var valid0 = true;}if(valid0){if(data.dataschema !== undefined){let data5 = data.dataschema;const _errs16 = errors;const _errs17 = errors;if((typeof data5 !== "string") && (data5 !== null)){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/type",keyword:"type",params:{type: schema28.type},message:"must be string,null"}];return false;}if(errors === _errs17){if(errors === _errs17){if(typeof data5 === "string"){if(func8(data5) < 1){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats2(data5))){validate20.errors = [{instancePath:instancePath+"/dataschema",schemaPath:"#/definitions/dataschemadef/format",keyword:"format",params:{format: "uri"},message:"must match format \""+"uri"+"\""}];return false;}}}}}var valid0 = _errs16 === errors;}else {var valid0 = true;}if(valid0){if(data.subject !== undefined){let data6 = data.subject;const _errs19 = errors;const _errs20 = errors;if((typeof data6 !== "string") && (data6 !== null)){validate20.errors = [{instancePath:instancePath+"/subject",schemaPath:"#/definitions/subjectdef/type",keyword:"type",params:{type: schema29.type},message:"must be string,null"}];return false;}if(errors === _errs20){if(typeof data6 === "string"){if(func8(data6) < 1){validate20.errors = [{instancePath:instancePath+"/subject",schemaPath:"#/definitions/subjectdef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}}}var valid0 = _errs19 === errors;}else {var valid0 = true;}if(valid0){if(data.time !== undefined){let data7 = data.time;const _errs22 = errors;const _errs23 = errors;if((typeof data7 !== "string") && (data7 !== null)){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/type",keyword:"type",params:{type: schema30.type},message:"must be string,null"}];return false;}if(errors === _errs23){if(errors === _errs23){if(typeof data7 === "string"){if(func8(data7) < 1){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"}];return false;}else {if(!(formats4.validate(data7))){validate20.errors = [{instancePath:instancePath+"/time",schemaPath:"#/definitions/timedef/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}];return false;}}}}}var valid0 = _errs22 === errors;}else {var valid0 = true;}if(valid0){if(data.data !== undefined){let data8 = data.data;const _errs25 = errors;if((((typeof data8 != "object") && (typeof data8 !== "string")) && (!((typeof data8 == "number") && (isFinite(data8))))) && (typeof data8 !== "boolean")){validate20.errors = [{instancePath:instancePath+"/data",schemaPath:"#/definitions/datadef/type",keyword:"type",params:{type: schema31.type},message:"must be object,string,number,array,boolean,null"}];return false;}var valid0 = _errs25 === errors;}else {var valid0 = true;}if(valid0){if(data.data_base64 !== undefined){let data9 = data.data_base64;const _errs28 = errors;if((typeof data9 !== "string") && (data9 !== null)){validate20.errors = [{instancePath:instancePath+"/data_base64",schemaPath:"#/definitions/data_base64def/type",keyword:"type",params:{type: schema32.type},message:"must be string,null"}];return false;}var valid0 = _errs28 === errors;}else {var valid0 = true;}}}}}}}}}}}}else {validate20.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate20.errors = vErrors;return errors === 0;} \ No newline at end of file