cloudevent
Advanced tools
Comparing version 0.6.0 to 0.7.0
# Change Log | ||
## [0.7.0](https://github.com/smartiniOnGitHub/cloudevent.js/releases/tag/0.7.0) (2020-09-20) | ||
Summary Changelog: | ||
- Implement the [v1.0 - CloudEvents Spec](https://github.com/cloudevents/spec/releases/tag/v1.0) | ||
with all breaking changes since its v0.3 | ||
- Update dependencies for the development environment | ||
- Update JSON Schema to that of spec v1.0 | ||
- Feature: keep compatibility with Node.js 8 (but this is latest release that supports it) | ||
- Breaking change: updated attributes as per spec | ||
- Breaking change: updated my extension/s to be compliant with updated spec | ||
- Breaking change: updated getter method 'payload' to return parsed JSON data | ||
if datacontenttype is default or json-derived, and unparsed data in other cases; | ||
if data_base64 is defined, an uncoded version of it will be returned | ||
- Feature: implement a getter method 'dataType' to tell if data is text or binary encoded | ||
- Feature: added as validation option a user defined function to validate data with dataschema | ||
- General: ensured some CloudEvent serialized instances pass some online validators for this spec version; | ||
for example [this](https://www.itb.ec.europa.eu/json/cloudevents/upload) from its Web UI | ||
## [0.6.0](https://github.com/smartiniOnGitHub/cloudevent.js/releases/tag/0.6.0) (2019-10-23) | ||
@@ -4,0 +21,0 @@ Summary Changelog: |
@@ -41,10 +41,11 @@ /* | ||
// create some sample instances but without mandatory fields (for validation) ... | ||
console.log('\nCreation of some CloudEvent instances, and related diagnostics:') | ||
// create some sample instances but without mandatory fields (so not good for validation) ... | ||
// note that errors will be thrown at instance creation only when strict mode is true | ||
console.log('\nCreation of some CloudEvent (ce) instances, and related diagnostics:') | ||
const ceEmpty = new CloudEvent() // create an empty CloudEvent instance (not valid for the validator, even in default case, when strict mode flag is disabled) | ||
assert(ceEmpty !== null) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceEmpty, 'ceEmpty')}`) | ||
console.log(`ce dump (but not good for validation): ${T.dumpObject(ceEmpty, 'ceEmpty')}`) | ||
const ceMinimalMandatoryUndefinedNoStrict = new CloudEvent(undefined, undefined, undefined, undefined, { strict: false }) // expected success | ||
assert(ceMinimalMandatoryUndefinedNoStrict !== null) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceMinimalMandatoryUndefinedNoStrict, 'ceMinimalMandatoryUndefinedNoStrict')}`) | ||
console.log(`ce dump (but not good for validation): ${T.dumpObject(ceMinimalMandatoryUndefinedNoStrict, 'ceMinimalMandatoryUndefinedNoStrict')}`) | ||
// const ceMinimalMandatoryUndefinedStrict = new CloudEvent(undefined, undefined, undefined, undefined, { strict: true }) // expected failure | ||
@@ -55,5 +56,5 @@ // assert(ceMinimalMandatoryUndefinedStrict == null) // no, ReferenceError: ceMinimalMandatoryUndefinedStrict is not defined | ||
const ceCommonOptions = { | ||
time: new Date(), | ||
time: new Date(), // same as default | ||
datacontenttype: 'application/json', | ||
schemaurl: 'http://my-schema.localhost.localdomain/v1/', | ||
dataschema: 'http://my-schema.localhost.localdomain/v1/', | ||
subject: 'subject', | ||
@@ -63,9 +64,11 @@ strict: false // same as default | ||
const ceCommonOptionsStrict = { ...ceCommonOptions, strict: true } | ||
const ceCommonExtensions = { exampleExtension: 'value' } | ||
const ceCommonExtensions = { exampleextension: 'value' } | ||
const ceNamespace = 'com.github.smartiniOnGitHub.cloudeventjs.testevent-v1.0.0' | ||
const ceServerUrl = '/test' | ||
const ceCommonData = { hello: 'world', year: 2019 } | ||
// const ceDataAsString = 'Hello World, 2019' | ||
const ceCommonData = { hello: 'world', year: 2020, enabled: true } | ||
const ceDataAsJSONString = '{ "hello": "world", "year": 2020, "enabled": true }' | ||
const ceDataAsString = 'Hello World, 2020' | ||
const ceDataEncoded = 'SGVsbG8gV29ybGQsIDIwMjA=' | ||
// create some sample minimal instances, good even for validation ... | ||
// create a sample minimal instance ... | ||
const ceMinimal = new CloudEvent('1', // id | ||
@@ -77,6 +80,9 @@ ceNamespace, // type | ||
assert(ceMinimal !== null) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceMinimal, 'ceMinimal')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceMinimal, 'ceMinimal')}`) | ||
// create some instances with an undefined mandatory argument (handled by defaults), but with strict flag disabled: expected success ... | ||
// note that null values are not handled by default values, only undefined values ... | ||
// When creating some instances with an undefined mandatory argument (handled by defaults), | ||
// but with strict flag disabled success is expected, otherwise with strict flag enabled a failure is expected ... | ||
// In JavaScript, null values are not handled as default values, only undefined values ... | ||
// create a sample instance with most common attributes defined ... | ||
const ceFull = new CloudEvent('1/full', | ||
@@ -91,4 +97,4 @@ ceNamespace, | ||
assert(!ceFull.isStrict) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFull, 'ceFull')}`) | ||
const ceFullStrict = new CloudEvent('2/full-strict', | ||
console.log(`ce dump: ${T.dumpObject(ceFull, 'ceFull')}`) | ||
const ceFullStrict = new CloudEvent('1/full-strict', | ||
ceNamespace, | ||
@@ -104,3 +110,15 @@ ceServerUrl, | ||
assert(!CloudEvent.isStrictEvent(ceFull)) // the same, but using static method | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFullStrict, 'ceFullStrict')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrict, 'ceFullStrict')}`) | ||
// create an instance with a JSON string as data | ||
const ceFullStrictJSONTextData = new CloudEvent('2/full-strict-json-string-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
ceDataAsJSONString, // data | ||
ceCommonOptionsStrict, // use strict options | ||
ceCommonExtensions | ||
) | ||
assert(ceFullStrictJSONTextData !== null) | ||
assert(ceFullStrictJSONTextData.isStrict) | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrictJSONTextData, 'ceFullStrictJSONTextData')}`) | ||
console.log(`ce validation results on ceFullStrictJSONTextData = ${CloudEvent.validateEvent(ceFullStrictJSONTextData)}`) | ||
// create an instance that wrap an Error | ||
@@ -114,3 +132,3 @@ const error = new Error('sample error') | ||
}) | ||
const ceErrorStrict = new CloudEvent('2/error-strict', | ||
const ceErrorStrict = new CloudEvent('3/error-strict', | ||
ceNamespace, | ||
@@ -124,5 +142,5 @@ ceServerUrl, | ||
assert(ceErrorStrict.isStrict) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceErrorStrict, 'ceErrorStrict')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceErrorStrict, 'ceErrorStrict')}`) | ||
// create an instance with a different content type | ||
const ceFullStrictOtherContentType = new CloudEvent('3/full-strict-other-content-type', | ||
const ceFullStrictOtherContentType = new CloudEvent('4/full-strict-other-content-type', | ||
ceNamespace, | ||
@@ -132,3 +150,3 @@ ceServerUrl, | ||
ceCommonData, // data | ||
{ ...ceCommonOptionsStrict, datacontenttype: 'application/xml' }, // use common strict options, but set strict mode to true | ||
{ ...ceCommonOptionsStrict, datacontenttype: 'application/xml' }, // use common strict options | ||
ceCommonExtensions | ||
@@ -140,3 +158,29 @@ ) | ||
assert(!CloudEvent.isStrictEvent(ceFull)) // the same, but using static method | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFullStrictOtherContentType, 'ceFullStrictOtherContentType')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrictOtherContentType, 'ceFullStrictOtherContentType')}`) | ||
// create an instance with data as a string, but not strict (to validate it even in strict mode) | ||
const ceFullTextData = new CloudEvent('5/no-strict-text-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
ceDataAsString, // data | ||
ceCommonOptions, // use common options | ||
ceCommonExtensions | ||
) | ||
assert(ceFullTextData !== null) | ||
assert(!ceFullTextData.isStrict) | ||
assert(ceFullTextData.payload === ceDataAsString) // returned data is transformed | ||
console.log(`ce dump: ${T.dumpObject(ceFullTextData, 'ceFullTextData')}`) | ||
console.log(`ce validation results on ceFullTextData (no strict validation) = ${CloudEvent.validateEvent(ceFullTextData)}`) | ||
console.log(`ce validation results on ceFullTextData (with strict validation) = ${CloudEvent.validateEvent(ceFullTextData, { strict: true })}`) | ||
// create an instance with data encoded in base64 | ||
const ceFullStrictBinaryData = new CloudEvent('6/full-strict-binary-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
null, // data | ||
{ ...ceCommonOptionsStrict, datainbase64: ceDataEncoded }, // use common strict options, and set binary data in base64 | ||
ceCommonExtensions | ||
) | ||
assert(ceFullStrictBinaryData !== null) | ||
assert(ceFullStrictBinaryData.isStrict) | ||
assert(ceFullStrictBinaryData.payload === ceDataAsString) // returned data is transformed | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrictBinaryData, 'ceFullStrictBinaryData')}`) | ||
@@ -150,4 +194,8 @@ // validate/check if valid instances (optional) | ||
assert(ceFullStrict.isValid()) | ||
assert(ceFullStrictJSONTextData.isValid()) | ||
assert(ceErrorStrict.isValid()) | ||
assert(ceFullStrictOtherContentType.isValid()) | ||
assert(ceFullTextData.isValid()) | ||
assert(!ceFullTextData.isValid({ strict: true })) | ||
assert(ceFullStrictBinaryData.isValid()) | ||
// the same, but using static method | ||
@@ -159,4 +207,8 @@ assert(!CloudEvent.isValidEvent(ceEmpty)) | ||
assert(CloudEvent.isValidEvent(ceFullStrict)) | ||
assert(CloudEvent.isValidEvent(ceFullStrictJSONTextData)) | ||
assert(CloudEvent.isValidEvent(ceErrorStrict)) | ||
assert(CloudEvent.isValidEvent(ceFullStrictOtherContentType)) | ||
assert(CloudEvent.isValidEvent(ceFullTextData)) | ||
assert(!CloudEvent.isValidEvent(ceFullTextData, { strict: true })) | ||
assert(CloudEvent.isValidEvent(ceFullStrictBinaryData)) | ||
assert(CloudEvent.validateEvent(ceEmpty).length === 3) | ||
@@ -172,5 +224,14 @@ assert(CloudEvent.validateEvent(ceEmpty, { strict: true }).length === 5) | ||
assert(CloudEvent.validateEvent(ceFullStrict, { strict: true }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictJSONTextData).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictJSONTextData, { strict: false }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictJSONTextData, { strict: true }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictOtherContentType).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictOtherContentType, { strict: false }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictOtherContentType, { strict: true }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullTextData).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullTextData, { strict: false }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullTextData, { strict: true }).length === 1) | ||
assert(CloudEvent.validateEvent(ceFullStrictBinaryData).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictBinaryData, { strict: false }).length === 0) | ||
assert(CloudEvent.validateEvent(ceFullStrictBinaryData, { strict: true }).length === 0) | ||
// some diagnostic info | ||
@@ -198,3 +259,3 @@ console.log('\nSome expected validation errors:') | ||
// encoder: (data) => '<data "encoder"="sample" />', | ||
encodedData: '<data "hello"="world" "year"="2019" />', | ||
encodedData: '<data "hello"="world" "year"="2020" />', | ||
onlyValid: true | ||
@@ -205,3 +266,3 @@ }) | ||
// encoder: (data) => '<data "encoder"="sample" />', | ||
encodedData: '<data "hello"="world" "year"="2019" />', | ||
encodedData: '<data "hello"="world" "year"="2020" />', | ||
onlyValid: true | ||
@@ -223,10 +284,10 @@ }) | ||
assert(CloudEvent.isCloudEvent(ceFullDeserialized)) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFullDeserialized, 'ceFullDeserialized')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceFullDeserialized, 'ceFullDeserialized')}`) | ||
const ceFullStrictDeserializedOnlyValid = CloudEvent.deserializeEvent(ceFullStrictSerialized, { onlyValid: true }) | ||
assert(ceFullStrictDeserializedOnlyValid !== null) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFullStrictDeserializedOnlyValid, 'ceFullStrictDeserializedOnlyValid')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrictDeserializedOnlyValid, 'ceFullStrictDeserializedOnlyValid')}`) | ||
// non default contenttype | ||
const ceFullStrictOtherContentTypeDeserialized = CloudEvent.deserializeEvent(ceFullStrictOtherContentTypeSerialized, { | ||
// decoder: (data) => { decoder: 'Sample' }, | ||
decodedData: { hello: 'world', year: 2019 }, | ||
decodedData: { hello: 'world', year: 2020 }, | ||
onlyValid: true | ||
@@ -238,3 +299,3 @@ }) | ||
assert(CloudEvent.isCloudEvent(ceFullStrictOtherContentTypeDeserialized)) | ||
console.log(`cloudEvent dump: ${T.dumpObject(ceFullStrictOtherContentTypeDeserialized, 'ceFullStrictOtherContentTypeDeserialized')}`) | ||
console.log(`ce dump: ${T.dumpObject(ceFullStrictOtherContentTypeDeserialized, 'ceFullStrictOtherContentTypeDeserialized')}`) | ||
@@ -241,0 +302,0 @@ // then use (validate/send/store/etc) deserialized instances ... |
{ | ||
"name": "cloudevent", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"description": "JavaScript/Node.js implementation of the CloudEvents standard format", | ||
@@ -29,8 +29,8 @@ "main": "src/index", | ||
"esdoc-node": "^1.0.5", | ||
"standard": "^14.3.1", | ||
"tap": "^14.8.2" | ||
"standard": "^14.3.4", | ||
"tap": "^14.10.8" | ||
}, | ||
"peerDependencies": {}, | ||
"engines": { | ||
"node": ">=8.16.2" | ||
"node": ">=8.17.0" | ||
}, | ||
@@ -50,3 +50,4 @@ "homepage": "https://github.com/smartiniOnGitHub/cloudevent.js#readme", | ||
"author": "Sandro Martini <sandro.martini@gmail.com>", | ||
"license": "Apache-2.0" | ||
"license": "Apache-2.0", | ||
"snyk": true | ||
} |
@@ -14,3 +14,3 @@ # cloudevent / cloudevent.js | ||
Current release implements the v0.3 of the CloudEvents Spec. | ||
Current release implements the v1.0 of the CloudEvents Spec. | ||
@@ -50,3 +50,4 @@ The purpose of this library is to create instances of CloudEvents in a simple way | ||
```js | ||
// create some sample instances but without mandatory fields (for validation) ... | ||
// create some sample instances but without mandatory fields (so not good for validation) ... | ||
// note that errors will be thrown at instance creation only when strict mode is true | ||
const ceEmpty = new CloudEvent() // create an empty CloudEvent instance (not valid for the validator, even in default case, when strict mode flag is disabled) | ||
@@ -58,5 +59,5 @@ const ceMinimalMandatoryUndefinedNoStrict = new CloudEvent(undefined, undefined, undefined, undefined, { strict: false }) // expected success | ||
const ceCommonOptions = { | ||
time: new Date(), | ||
time: new Date(), // same as default | ||
datacontenttype: 'application/json', | ||
schemaurl: 'http://my-schema.localhost.localdomain/v1/', | ||
dataschema: 'http://my-schema.localhost.localdomain/v1/', | ||
subject: 'subject', | ||
@@ -66,8 +67,11 @@ strict: false // same as default | ||
const ceCommonOptionsStrict = { ...ceCommonOptions, strict: true } | ||
const ceCommonExtensions = { exampleExtension: 'value' } | ||
const ceCommonExtensions = { exampleextension: 'value' } | ||
const ceNamespace = 'com.github.smartiniOnGitHub.cloudeventjs.testevent-v1.0.0' | ||
const ceServerUrl = '/test' | ||
const ceCommonData = { hello: 'world', year: 2019 } | ||
const ceCommonData = { hello: 'world', year: 2020, enabled: true } | ||
const ceDataAsJSONString = '{ "hello": "world", "year": 2020, "enabled": true }' | ||
const ceDataAsString = 'Hello World, 2020' | ||
const ceDataEncoded = 'SGVsbG8gV29ybGQsIDIwMjA=' | ||
// create some sample minimal instances, good even for validation ... | ||
// create a sample minimal instance ... | ||
const ceMinimal = new CloudEvent('1', // id | ||
@@ -79,4 +83,7 @@ ceNamespace, // type | ||
// create some instances with an undefined mandatory argument (handled by defaults), but with strict flag disabled: expected success ... | ||
// note that null values are not handled by default values, only undefined values ... | ||
// When creating some instances with an undefined mandatory argument (handled by defaults), | ||
// but with strict flag disabled success is expected, otherwise with strict flag enabled a failure is expected ... | ||
// In JavaScript, null values are not handled as default values, only undefined values ... | ||
// create a sample instance with most common attributes defined ... | ||
const ceFull = new CloudEvent('1/full', | ||
@@ -99,2 +106,12 @@ ceNamespace, | ||
assert(!CloudEvent.isStrictEvent(ceFull)) // the same, but using static method | ||
// create an instance with a JSON string as data | ||
const ceFullStrictJSONTextData = new CloudEvent('2/full-strict-json-string-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
ceDataAsJSONString, // data | ||
ceCommonOptionsStrict, // use strict options | ||
ceCommonExtensions | ||
) | ||
assert(ceFullStrictJSONTextData !== null) | ||
assert(ceFullStrictJSONTextData.isStrict) | ||
// create an instance that wrap an Error | ||
@@ -108,3 +125,3 @@ const error = new Error('sample error') | ||
}) | ||
const ceErrorStrict = new CloudEvent('2/error-strict', | ||
const ceErrorStrict = new CloudEvent('3/error-strict', | ||
ceNamespace, | ||
@@ -121,3 +138,3 @@ ceServerUrl, | ||
// create an instance with a different content type | ||
const ceFullStrictOtherContentType = new CloudEvent('3/full-strict-other-content-type', | ||
const ceFullStrictOtherContentType = new CloudEvent('4/full-strict-other-content-type', | ||
ceNamespace, | ||
@@ -131,2 +148,24 @@ ceServerUrl, | ||
assert(ceFullStrictOtherContentType.isStrict) | ||
// create an instance with data as a string, but not strict (to validate it even in strict mode) | ||
const ceFullTextData = new CloudEvent('5/no-strict-text-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
ceDataAsString, // data | ||
ceCommonOptions, // use common options | ||
ceCommonExtensions | ||
) | ||
assert(ceFullTextData !== null) | ||
assert(!ceFullTextData.isStrict) | ||
assert(ceFullTextData.payload === ceDataAsString) // returned data is transformed | ||
// create an instance with data encoded in base64 | ||
const ceFullStrictBinaryData = new CloudEvent('6/full-strict-binary-data', | ||
ceNamespace, | ||
ceServerUrl, | ||
null, // data | ||
{ ...ceCommonOptionsStrict, datainbase64: ceDataEncoded }, // use common strict options, and set binary data in base64 | ||
ceCommonExtensions | ||
) | ||
assert(ceFullStrictBinaryData !== null) | ||
assert(ceFullStrictBinaryData.isStrict) | ||
assert(ceFullStrictBinaryData.payload === ceDataAsString) // returned data is transformed | ||
``` | ||
@@ -166,3 +205,3 @@ | ||
// encoder: (data) => '<data "encoder"="sample" />', | ||
encodedData: '<data "hello"="world" "year"="2019" />', | ||
encodedData: '<data "hello"="world" "year"="2020" />', | ||
onlyValid: true | ||
@@ -172,3 +211,3 @@ }) | ||
// encoder: (data) => '<data "encoder"="sample" />', | ||
encodedData: '<data "hello"="world" "year"="2019" />', | ||
encodedData: '<data "hello"="world" "year"="2020" />', | ||
onlyValid: true | ||
@@ -200,3 +239,3 @@ }) | ||
// decoder: (data) => { decoder: 'Sample' }, | ||
decodedData: { hello: 'world', year: 2019 }, | ||
decodedData: { hello: 'world', year: 2020 }, | ||
onlyValid: true | ||
@@ -221,3 +260,3 @@ }) | ||
Node.js 8.16.x or later. | ||
Node.js 8 LTS (but recommended 8.17.0) or later. | ||
@@ -228,7 +267,8 @@ | ||
Note that in this implementation there is even the ability to validate CloudEvent instances | ||
in a stricter way, by setting to true the attribute 'strict' in options in constructor options; | ||
or specify it during validation. | ||
That attribute when set will be put in the 'extensions' standard attribute of a CloudEvent. | ||
in a stricter way, by setting to true the attribute 'strict' in constructor options; | ||
that attribute (when set) will be put in the extensions of the instance. | ||
Otherwise you can specify it only during validation, in validation options. | ||
You can find Code Documentation for the API of the library [here](https://smartiniongithub.github.io/cloudevent.js/). | ||
You can find Code Documentation for the API of the library | ||
[here](https://smartiniongithub.github.io/cloudevent.js/). | ||
@@ -243,3 +283,3 @@ See the CloudEvents Specification [here](https://github.com/cloudevents/spec). | ||
and for example add a version in the 'type' attribute | ||
(for example '-v1.0.0' at the end of its base value, or at the end of its full value) , | ||
(for example '-v1.0.0' at the end of its base value, or at the end of its full value), | ||
or into the 'schemaurl' attribute but only its major version | ||
@@ -251,2 +291,6 @@ (like '-v1' or '/v1/' at the end). | ||
but without dots, so like 'com_github_smartiniOnGitHub_cloudevent'. | ||
Since v1.0 of the spec, some properties has been removed/simplified; | ||
extension properties must be simple (no nested properties) | ||
and must contain only lowercase letters and numbers in the name (and less than 20 chars in total); | ||
so for example my strict extension now is 'strictvalidation' with a boolean value. | ||
@@ -253,0 +297,0 @@ |
@@ -51,16 +51,16 @@ /* | ||
* time (timestamp/date, default now), | ||
* datacontentencoding (string) optional in most cases here, | ||
* datacontenttype (string, default 'application/json') tell how the data attribute must be encoded, | ||
* schemaurl (uri) optional, | ||
* datainbase64 (string) base64 encoded value for the data (data attribute must not be present when this is defined), | ||
* datacontenttype (string, default 'application/json') is the content type of the data attribute, | ||
* dataschema (uri) optional, reference to the schema that data adheres to, | ||
* subject (string) optional, describes the subject of the event in the context of the event producer (identified by source), | ||
* strict (boolean, default false) tell if object instance will be validated in a more strict way | ||
* @param {object} extensions optional, contains extension properties (recommended in nested objects) but if given any object must contain at least 1 property (key/value) | ||
* @param {object} extensions optional, contains extension properties (each extension as a key/value property, and no nested objects) but if given any object must contain at least 1 property | ||
* @throws {Error} if strict is true and id or type is undefined or null | ||
* @throws {Error} if datacontentencoding is defined and data is not a string or if encoding is not 'base64' | ||
* @throws {Error} if data and data_base64 are defined | ||
*/ | ||
constructor (id, type, source, data, { | ||
time = new Date(), | ||
datacontentencoding, | ||
datainbase64, | ||
datacontenttype = CloudEvent.datacontenttypeDefault(), | ||
schemaurl, | ||
dataschema, | ||
subject, | ||
@@ -73,4 +73,7 @@ strict = false | ||
if (!id || !type || !source) { | ||
throw new Error('Unable to create CloudEvent instance, mandatory field missing') | ||
throw new Error('Unable to create CloudEvent instance, mandatory attributes missing') | ||
} | ||
if (V.isDefinedAndNotNull(data) && V.isDefinedAndNotNull(datainbase64)) { | ||
throw new Error('Unable to create CloudEvent instance, data and data_base64 attributes are exclusive') | ||
} | ||
} | ||
@@ -120,12 +123,9 @@ | ||
this.specversion = this.constructor.version() | ||
/** | ||
* The content encoding for the data attribute | ||
* for when the data field must be encoded as a string. | ||
* This must be set if the data attribute contains string-encoded binary data, | ||
* otherwise it must not be set. | ||
* As (arbitrary) limitation, only 'base64' encoding is supported here. | ||
* The real event data, but encoded in base64 format. | ||
* @type {string} | ||
* @private | ||
*/ | ||
this.datacontentencoding = datacontentencoding | ||
this.data_base64 = datainbase64 | ||
/** | ||
@@ -138,2 +138,8 @@ * The MIME Type for the encoding of the data attribute, when serialized. | ||
/** | ||
* The URI of the schema for event data, if any. | ||
* @type {uri} | ||
* @private | ||
*/ | ||
this.dataschema = dataschema | ||
/** | ||
* The event timestamp. | ||
@@ -147,8 +153,2 @@ * Copy the original object to avoid changing objects that could be shared. | ||
/** | ||
* The URL of schema for the event, if any. | ||
* @type {uri} | ||
* @private | ||
*/ | ||
this.schemaurl = schemaurl | ||
/** | ||
* The subject of the event in the context of the event producer. | ||
@@ -184,3 +184,3 @@ * @type {string} | ||
static version () { | ||
return '0.3' | ||
return '1.0' | ||
} | ||
@@ -242,4 +242,4 @@ | ||
} | ||
if (V.isDefinedAndNotNull(event.com_github_smartiniOnGitHub_cloudevent)) { | ||
return event.com_github_smartiniOnGitHub_cloudevent.strict === true | ||
if (V.isDefinedAndNotNull(event.strictvalidation)) { | ||
return event.strictvalidation === true | ||
} else { | ||
@@ -268,4 +268,3 @@ return false | ||
} | ||
obj.com_github_smartiniOnGitHub_cloudevent = {} | ||
obj.com_github_smartiniOnGitHub_cloudevent.strict = strict | ||
obj.strictvalidation = strict | ||
} | ||
@@ -283,2 +282,3 @@ | ||
* @throws {Error} if obj is undefined or null | ||
* @throws {Error} if strictvalidation property is undefined or null | ||
*/ | ||
@@ -289,11 +289,7 @@ static getStrictExtensionOfEvent (obj = {}) { | ||
} | ||
const myExtensions = obj.com_github_smartiniOnGitHub_cloudevent || {} | ||
if (!V.isObjectPlain(myExtensions)) { | ||
throw new TypeError('The property com_github_smartiniOnGitHub_cloudevent is not an object instance') | ||
const myExtensionStrict = obj.strictvalidation || false | ||
if (!V.isBoolean(myExtensionStrict)) { | ||
throw new TypeError("Extension property 'strictvalidation' has not a boolean value") | ||
} | ||
const strict = myExtensions.strict || false | ||
if (!V.isBoolean(strict)) { | ||
throw new TypeError('The given strict flag is not a boolean instance') | ||
} | ||
return strict | ||
return myExtensionStrict | ||
} | ||
@@ -308,3 +304,3 @@ | ||
* @param {object} [obj={}] the object to fill, that will be enhanced inplace | ||
* @param {object} [extensions=null] the extensions to fill (maybe already populated) | ||
* @param {object} [extensions=null] the extensions to fill (each extension as a key/value property, and no nested properties) | ||
* @throws {TypeError} if obj is not an object, or strict is not a flag | ||
@@ -367,6 +363,8 @@ * @throws {Error} if obj is undefined or null, or strict is undefined or null | ||
* @param {!object} event the CloudEvent to validate | ||
* @param {object} [options={}] containing: strict (boolean, default false) to validate it in a more strict way | ||
* @param {object} [options={}] containing: | ||
* strict (boolean, default false) to validate it in a more strict way, | ||
* dataschemavalidator (function(data, dataschema) boolean, optional) a function to validate data of current CloudEvent instance with its dataschema | ||
* @return {object[]} an array of (non null) validation errors, or at least an empty array | ||
*/ | ||
static validateEvent (event, { strict = false } = {}) { | ||
static validateEvent (event, { strict = false, dataschemavalidator = null } = {}) { | ||
if (V.isUndefinedOrNull(event)) { | ||
@@ -387,4 +385,4 @@ return [new Error('CloudEvent undefined or null')] | ||
ve.push(V.ensureIsStringNotEmpty(event.source, 'source')) | ||
if (V.isDefinedAndNotNull(event.schemaurl)) { | ||
ve.push(V.ensureIsStringNotEmpty(event.schemaurl, 'schemaurl')) | ||
if (V.isDefinedAndNotNull(event.dataschema)) { | ||
ve.push(V.ensureIsStringNotEmpty(event.dataschema, 'dataschema')) | ||
} | ||
@@ -394,4 +392,7 @@ if (V.isDefinedAndNotNull(event.subject)) { | ||
} | ||
if (V.isDefinedAndNotNull(event.datacontentencoding)) { | ||
ve.push(V.ensureIsStringNotEmpty(event.datacontentencoding, 'datacontentencoding')) | ||
if (V.isDefinedAndNotNull(event.data_base64)) { | ||
ve.push(V.ensureIsStringNotEmpty(event.data_base64, 'data_base64')) | ||
if (V.isDefinedAndNotNull(event.data)) { | ||
ve.push(new Error('data and data_base64 attributes are exclusive')) | ||
} | ||
} | ||
@@ -403,12 +404,18 @@ | ||
if (V.isDefinedAndNotNull(event.data)) { | ||
if (V.isDefinedAndNotNull(event.datacontentencoding)) { | ||
// ensure data is a string in this case | ||
ve.push(V.ensureIsString(event.data, 'data')) | ||
} else if (event.datacontenttype !== CloudEvent.datacontenttypeDefault()) { | ||
if (event.datacontenttype === CloudEvent.datacontenttypeDefault()) { | ||
// if it's a string, ensure it's a valid JSON representation, | ||
// otherwise ensure data is a plain object or collection, but not a string in this case | ||
if (V.isString(event.data)) { | ||
try { | ||
JSON.parse(event.data) | ||
} catch (e) { | ||
ve.push(new Error('data is not a valid JSON string')) | ||
} | ||
} else { | ||
ve.push(V.ensureIsObjectOrCollectionNotString(event.data, 'data')) | ||
} | ||
} else { | ||
// ensure data is a plain object or collection, or even a string in this case | ||
// because in serialization/deserialization some validation can occur on the transformed object | ||
ve.push(V.ensureIsObjectOrCollectionOrString(event.data, 'data')) | ||
} else { | ||
// ensure data is a plain object or collection, but not a string in this case | ||
ve.push(V.ensureIsObjectOrCollectionNotString(event.data, 'data')) | ||
} | ||
@@ -419,3 +426,11 @@ } | ||
ve.push(V.ensureIsStringNotEmpty(event.datacontenttype, 'datacontenttype')) | ||
ve.push(V.ensureIsURI(event.schemaurl, null, 'schemaurl')) | ||
ve.push(V.ensureIsURI(event.dataschema, null, 'dataschema')) | ||
if (V.isFunction(dataschemavalidator)) { | ||
try { | ||
const success = dataschemavalidator(event.data, event.dataschema) | ||
if (success === false) throw Error() | ||
} catch (e) { | ||
ve.push(new Error('data does not respect the dataschema for the given validator')) | ||
} | ||
} | ||
if (V.isDefinedAndNotNull(event.extensions)) { | ||
@@ -425,2 +440,7 @@ // get extensions via its getter | ||
// error for extensions defined but empty (without properties), moved in constructor | ||
// then check for each extension name and value | ||
for (const [key, value] of Object.entries(event.extensions)) { | ||
if (!CloudEvent.isExtensionNameValid(key)) ve.push(new Error(`extension name '${key}' not valid`)) | ||
if (!CloudEvent.isExtensionValueValid(value)) ve.push(new Error(`extension value '${value}' not valid for extension '${key}'`)) | ||
} | ||
} | ||
@@ -439,7 +459,9 @@ } | ||
* @param {!object} event the CloudEvent to validate | ||
* @param {object} [options={}] containing: strict (boolean, default false) to validate it in a more strict way | ||
* @param {object} [options={}] containing: | ||
* strict (boolean, default false) to validate it in a more strict way, | ||
* dataschemavalidator (function(data, dataschema) boolean, optional) a function to validate data of current CloudEvent instance with its dataschema | ||
* @return {boolean} true if valid, otherwise false | ||
*/ | ||
static isValidEvent (event, { strict = false } = {}) { | ||
const validationErrors = CloudEvent.validateEvent(event, { strict }) | ||
static isValidEvent (event, { strict = false, dataschemavalidator = null } = {}) { | ||
const validationErrors = CloudEvent.validateEvent(event, { strict, dataschemavalidator }) | ||
const size = V.getSize(validationErrors) | ||
@@ -488,7 +510,2 @@ return (size === 0) | ||
switch (key) { | ||
case 'data': | ||
// return data as is, or encoded or nothing (if not supported) | ||
if (V.isUndefinedOrNull(event.datacontentencoding)) return value | ||
if (event.datacontentencoding === 'Base64') return T.stringToBase64(this.data) | ||
else return undefined | ||
case 'extensions': | ||
@@ -550,2 +567,3 @@ // filtering out top level extensions (if any) | ||
if (!V.isObject(parsed) || V.isArray(parsed)) throw new Error(`Wrong deserialized data: '${ser}' must represent an object and not an array or a string or other.`) | ||
if (!V.isStringNotEmpty(parsed.specversion) || parsed.specversion !== CloudEvent.version()) throw new Error(`Unable to deserialize, not compatible specversion: got '${parsed.specversion}' expected '${CloudEvent.version()}'.`) | ||
@@ -555,9 +573,2 @@ const strict = CloudEvent.getStrictExtensionOfEvent(parsed) | ||
if (V.isDefinedAndNotNull(parsed.datacontentencoding)) { | ||
if (V.isStringNotEmpty(parsed.data)) { | ||
// decode the given data | ||
if (parsed.datacontentencoding === 'Base64') parsed.data = T.stringFromBase64(parsed.data) | ||
} | ||
} | ||
// fill a new CludEvent instance with parsed data | ||
@@ -570,5 +581,5 @@ const ce = new CloudEvent(parsed.id, | ||
time: T.timestampFromString(parsed.time, timezoneOffset), | ||
datacontentencoding: parsed.datacontentencoding, | ||
datainbase64: parsed.data_base64, | ||
datacontenttype: parsed.datacontenttype, | ||
schemaurl: parsed.schemaurl, | ||
dataschema: parsed.dataschema, | ||
subject: parsed.subject, | ||
@@ -628,2 +639,35 @@ strict: strict | ||
/** | ||
* Tell if the given extension name is valid, to respect the spec. | ||
* Should not be used outside CloudEvent. | ||
* | ||
* @private | ||
* @static | ||
* @param {!object|!string} name the name to check | ||
* @return {boolean} true if it's an extension name valid, otherwise false | ||
* @throws {TypeError} if name is not a string | ||
* @throws {Error} if name is undefined or null | ||
*/ | ||
static isExtensionNameValid (name) { | ||
if (V.isUndefinedOrNull(name)) throw new Error('Extension name undefined or null') | ||
if (!V.isString(name)) throw new TypeError('Extension name must be a string') | ||
return name.match(/^[a-z0-9]{1,20}$/) | ||
} | ||
/** | ||
* Tell if the given extension value is valid, to respect the spec. | ||
* Should not be used outside CloudEvent. | ||
* | ||
* @private | ||
* @static | ||
* @param {!string|!boolean|!number} value the object to check | ||
* @return {boolean} true if it's an extension value valid, otherwise false | ||
* @throws {Error} if value is undefined | ||
*/ | ||
static isExtensionValueValid (value) { | ||
if (V.isUndefined(value)) throw new Error('Extension value undefined') | ||
if (!V.isString(value) && !V.isBoolean(value) && !V.isNumber(value)) return false | ||
return true | ||
} | ||
/** | ||
* Get the JSON Schema for a CloudEvent. | ||
@@ -658,10 +702,9 @@ * Note that it's not used in standard serialization to JSON, | ||
datacontenttype: { type: 'string' }, | ||
// data: { type: ['object', 'string'] }, | ||
data: { type: ['object', 'string'] }, | ||
data_base64: { type: 'string' }, | ||
dataschema: { type: 'string', format: 'uri' }, | ||
// time: { type: 'string', format: 'date-time' }, | ||
schemaurl: { type: 'string', format: 'uri-reference' }, | ||
subject: { type: 'string', minLength: 1 } | ||
}, | ||
required: [ | ||
'specversion', 'id', 'type', 'source' | ||
], | ||
required: ['specversion', 'id', 'type', 'source'], | ||
additionalProperties: true // to handle data, and maybe other (non-standard) properties (extensions) | ||
@@ -690,7 +733,9 @@ } | ||
* | ||
* @param {object} [options={}] containing: strict (boolean, default false) to validate it in a more strict way | ||
* @param {object} [options={}] containing: | ||
* strict (boolean, default false) to validate it in a more strict way, | ||
* dataschemavalidator (function(data, dataschema) boolean, optional) a function to validate data of current CloudEvent instance with its dataschema | ||
* @return {object[]} an array of (non null) validation errors, or at least an empty array | ||
*/ | ||
validate ({ strict = false } = {}) { | ||
return this.constructor.validateEvent(this, { strict }) | ||
validate ({ strict = false, dataschemavalidator = null } = {}) { | ||
return this.constructor.validateEvent(this, { strict, dataschemavalidator }) | ||
} | ||
@@ -703,7 +748,9 @@ | ||
* | ||
* @param {object} [options={}] containing: strict (boolean, default false) to validate it in a more strict way | ||
* @param {object} [options={}] containing: | ||
* strict (boolean, default false) to validate it in a more strict way, | ||
* dataschemavalidator (function(data, dataschema) boolean, optional) a function to validate data of current CloudEvent instance with its dataschema | ||
* @return {boolean} true if valid, otherwise false | ||
*/ | ||
isValid ({ strict = false } = {}) { | ||
return this.constructor.isValidEvent(this, { strict }) | ||
isValid ({ strict = false, dataschemavalidator = null } = {}) { | ||
return this.constructor.isValidEvent(this, { strict, dataschemavalidator }) | ||
} | ||
@@ -746,19 +793,52 @@ | ||
/** | ||
* Getter method to return a copy of CloudEvent data attribute, | ||
* or original data payload. | ||
* Getter method to return a copy of CloudEvent data attribute (or data_base64 if defined), | ||
* but transformed/decoded if possible. | ||
* | ||
* See {@link CloudEvent.data}. | ||
* See {@link CloudEvent.data}, {@link CloudEvent.data_base64}. | ||
* | ||
* @type {(object|Map|Set)} | ||
* @type {(object|Map|Set|string)} | ||
*/ | ||
get payload () { | ||
if (V.isString(this.data)) { | ||
// handle an edge case: if data is a String, I need to clone in a different way ... | ||
return this.data.slice() | ||
if (V.isDefinedAndNotNull(this.data) && !V.isDefinedAndNotNull(this.data_base64)) { | ||
if (this.isDatacontenttypeJSON) { | ||
try { | ||
return JSON.parse(this.data) | ||
} catch (e) { | ||
// fallback in case of bad data (not parseable) | ||
if (V.isString(this.data)) { | ||
return this.data.slice() | ||
} else { | ||
return { ...this.data } | ||
} | ||
} | ||
} else if (V.isString(this.data)) { | ||
// handle an edge case: if data is a String, I need to clone in a different way ... | ||
return this.data.slice() | ||
} else { | ||
return { ...this.data } | ||
} | ||
} else if (V.isDefinedAndNotNull(this.data_base64)) { | ||
return T.stringFromBase64(this.data_base64) | ||
} | ||
// else | ||
return { ...this.data } | ||
// else return the same empty object | ||
return this.data | ||
} | ||
/** | ||
* Getter method to tell if CloudEvent data is text or binary, | ||
* or unknown if not clear. | ||
* | ||
* @type {string} | ||
*/ | ||
get dataType () { | ||
if (V.isDefinedAndNotNull(this.data) && !V.isDefinedAndNotNull(this.data_base64)) { | ||
return 'Text' | ||
} else if (V.isDefinedAndNotNull(this.data_base64)) { | ||
return 'Binary' | ||
} | ||
// else return an unknown/wrong data type | ||
return 'Unknown' | ||
} | ||
/** | ||
* Getter method to return a copy of CloudEvent extensions. | ||
@@ -811,6 +891,6 @@ * | ||
'id', 'type', 'source', 'data', | ||
'time', 'datacontentencoding', 'datacontenttype', | ||
'schemaurl', 'subject' | ||
'time', 'data_base64', 'datacontenttype', | ||
'dataschema', 'subject' | ||
] | ||
module.exports = CloudEvent |
@@ -307,5 +307,5 @@ /* | ||
time: i.time, | ||
datacontentencoding: i.datacontentencoding, | ||
data_base64: i.data_base64, | ||
datacontenttype: i.datacontenttype, | ||
schemaurl: i.schemaurl, | ||
dataschema: i.dataschema, | ||
subject: i.subject, | ||
@@ -312,0 +312,0 @@ strict: CloudEvent.getStrictExtensionOfEvent(i) || options.strict |
@@ -225,3 +225,3 @@ /* | ||
static isFunction (arg) { | ||
return (typeof arg === 'function') | ||
return (Validator.isDefinedAndNotNull(arg) && (typeof arg === 'function')) | ||
} | ||
@@ -364,8 +364,8 @@ | ||
} else { | ||
// simple check if it's an URI (or better, a relative URL) | ||
if (arg.startsWith('/')) { | ||
// simple check if it's an URI (or better: a relative URL, or a full URI with scheme etc) | ||
const uriRegex = /^(\w+:|\/)/ // or /^(\w+:|\/)(.*)$/ for full match | ||
if (uriRegex.test(arg)) { | ||
return true | ||
} | ||
try { | ||
// return (new URL(arg) !== null) | ||
const u = new url.URL(arg) | ||
@@ -372,0 +372,0 @@ return (u !== null) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
145843
2693
296