Skip to content

Commit 15f6505

Browse files
authored
BREAKING CHANGE: use string instead of enum for Version (#561)
TypeScript does not consider enum values equivalent, even if the string representation is the same. So, when a module imports `cloudevents` and also has a dependency on `cloudevents` this can cause conflicts where the `CloudEvent.version` attribute is not considered equal when, in fact, it is. Changing the `enum` to a string is pretty straightforward, but should be considered a breaking change since TypeScript dependents will potentially fail the build with a change like this. Signed-off-by: Lance Ball <[email protected]>
1 parent 089520a commit 15f6505

File tree

11 files changed

+52
-53
lines changed

11 files changed

+52
-53
lines changed

src/event/cloudevent.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ import { validateCloudEvent } from "./spec";
1212
import { ValidationError, isBinary, asBase64, isValidType, base64AsBinary } from "./validation";
1313

1414
/**
15-
* An enum representing the CloudEvent specification version
15+
* Constants representing the CloudEvent specification version
1616
*/
17-
export const enum Version {
18-
V1 = "1.0",
19-
V03 = "0.3",
20-
}
17+
export const V1 = "1.0";
18+
export const V03 = "0.3";
2119

2220
/**
2321
* A CloudEvent describes event data in common formats to provide
@@ -28,7 +26,7 @@ export class CloudEvent<T = undefined> implements CloudEventV1<T> {
2826
id: string;
2927
type: string;
3028
source: string;
31-
specversion: Version;
29+
specversion: string;
3230
datacontenttype?: string;
3331
dataschema?: string;
3432
subject?: string;
@@ -69,7 +67,7 @@ export class CloudEvent<T = undefined> implements CloudEventV1<T> {
6967
this.source = properties.source as string;
7068
delete (properties as any).source;
7169

72-
this.specversion = (properties.specversion as Version) || Version.V1;
70+
this.specversion = (properties.specversion) || V1;
7371
delete properties.specversion;
7472

7573
this.datacontenttype = properties.datacontenttype;
@@ -103,9 +101,9 @@ export class CloudEvent<T = undefined> implements CloudEventV1<T> {
103101
delete properties.data;
104102

105103
// sanity checking
106-
if (this.specversion === Version.V1 && this.schemaurl) {
104+
if (this.specversion === V1 && this.schemaurl) {
107105
throw new TypeError("cannot set schemaurl on version 1.0 event");
108-
} else if (this.specversion === Version.V03 && this.dataschema) {
106+
} else if (this.specversion === V03 && this.dataschema) {
109107
throw new TypeError("cannot set dataschema on version 0.3 event");
110108
}
111109

src/event/spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import { ValidationError } from "./validation";
77

88
import { CloudEventV1 } from "./interfaces";
9-
import { Version } from "./cloudevent";
9+
import { V1 } from "./cloudevent";
1010
import validate from "../schema/v1";
1111

1212

1313
export function validateCloudEvent<T>(event: CloudEventV1<T>): boolean {
14-
if (event.specversion === Version.V1) {
14+
if (event.specversion === V1) {
1515
if (!validate(event)) {
1616
throw new ValidationError("invalid payload", (validate as any).errors);
1717
}

src/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { CloudEvent, Version } from "./event/cloudevent";
6+
import { CloudEvent, V1, V03 } from "./event/cloudevent";
77
import { ValidationError } from "./event/validation";
88
import { CloudEventV1, CloudEventV1Attributes } from "./event/interfaces";
99

1010
import { Options, TransportFunction, EmitterFunction, emitterFor, Emitter } from "./transport/emitter";
1111
import { httpTransport } from "./transport/http";
12-
import {
12+
import {
1313
Headers, Mode, Binding, HTTP, Kafka, KafkaEvent, KafkaMessage, Message, MQTT, MQTTMessage, MQTTMessageFactory,
1414
Serializer, Deserializer } from "./message";
1515

@@ -18,7 +18,8 @@ import CONSTANTS from "./constants";
1818
export {
1919
// From event
2020
CloudEvent,
21-
Version,
21+
V1,
22+
V03,
2223
ValidationError,
2324
Mode,
2425
HTTP,

src/message/http/headers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { PassThroughParser, DateParser, MappedParser } from "../../parsers";
77
import { CloudEventV1 } from "../..";
88
import { Headers } from "../";
9-
import { Version } from "../../event/cloudevent";
9+
import { V1 } from "../../event/cloudevent";
1010
import CONSTANTS from "../../constants";
1111

1212
export const allowedContentTypes = [CONSTANTS.DEFAULT_CONTENT_TYPE, CONSTANTS.MIME_JSON, CONSTANTS.MIME_OCTET_STREAM];
@@ -27,7 +27,7 @@ export const requiredHeaders = [
2727
export function headersFor<T>(event: CloudEventV1<T>): Headers {
2828
const headers: Headers = {};
2929
let headerMap: Readonly<{ [key: string]: MappedParser }>;
30-
if (event.specversion === Version.V1) {
30+
if (event.specversion === V1) {
3131
headerMap = v1headerMap;
3232
} else {
3333
headerMap = v03headerMap;

src/message/http/index.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { types } from "util";
77

8-
import { CloudEvent, CloudEventV1, CONSTANTS, Mode, Version } from "../..";
8+
import { CloudEvent, CloudEventV1, CONSTANTS, Mode, V1, V03 } from "../..";
99
import { Message, Headers, Binding } from "..";
1010

1111
import {
@@ -147,19 +147,19 @@ function getVersion(mode: Mode, headers: Headers, body: string | Record<string,
147147
return (body as Record<string, string>).specversion;
148148
}
149149
}
150-
return Version.V1;
150+
return V1;
151151
}
152152

153153
/**
154154
* Parses an incoming HTTP Message, converting it to a {CloudEvent}
155155
* instance if it conforms to the Cloud Event specification for this receiver.
156156
*
157157
* @param {Message} message the incoming HTTP Message
158-
* @param {Version} version the spec version of the incoming event
158+
* @param {string} version the spec version of the incoming event
159159
* @returns {CloudEvent} an instance of CloudEvent representing the incoming request
160160
* @throws {ValidationError} of the event does not conform to the spec
161161
*/
162-
function parseBinary<T>(message: Message, version: Version): CloudEvent<T> {
162+
function parseBinary<T>(message: Message, version: string): CloudEvent<T> {
163163
const headers = { ...message.headers };
164164
let body = message.body;
165165

@@ -169,7 +169,7 @@ function parseBinary<T>(message: Message, version: Version): CloudEvent<T> {
169169
const sanitizedHeaders = sanitize(headers);
170170

171171
const eventObj: { [key: string]: unknown | string | Record<string, unknown> } = {};
172-
const parserMap: Record<string, MappedParser> = version === Version.V03 ? v03binaryParsers : v1binaryParsers;
172+
const parserMap: Record<string, MappedParser> = version === V03 ? v03binaryParsers : v1binaryParsers;
173173

174174
for (const header in parserMap) {
175175
if (sanitizedHeaders[header]) {
@@ -206,11 +206,11 @@ function parseBinary<T>(message: Message, version: Version): CloudEvent<T> {
206206
* Creates a new CloudEvent instance based on the provided payload and headers.
207207
*
208208
* @param {Message} message the incoming Message
209-
* @param {Version} version the spec version of this message (v1 or v03)
209+
* @param {string} version the spec version of this message (v1 or v03)
210210
* @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload
211211
* @throws {ValidationError} if the payload and header combination do not conform to the spec
212212
*/
213-
function parseStructured<T>(message: Message, version: Version): CloudEvent<T> {
213+
function parseStructured<T>(message: Message, version: string): CloudEvent<T> {
214214
const payload = message.body;
215215
const headers = message.headers;
216216

@@ -227,7 +227,7 @@ function parseStructured<T>(message: Message, version: Version): CloudEvent<T> {
227227
const incoming = { ...(parser.parse(payload as string) as Record<string, unknown>) };
228228

229229
const eventObj: { [key: string]: unknown } = {};
230-
const parserMap: Record<string, MappedParser> = version === Version.V03 ? v03structuredParsers : v1structuredParsers;
230+
const parserMap: Record<string, MappedParser> = version === V03 ? v03structuredParsers : v1structuredParsers;
231231

232232
for (const key in parserMap) {
233233
const property = incoming[key];

test/integration/cloud_event_test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import path from "path";
77
import fs from "fs";
88

99
import { expect } from "chai";
10-
import { CloudEvent, CloudEventV1, ValidationError, Version } from "../../src";
10+
import { CloudEvent, CloudEventV1, ValidationError, V1 } from "../../src";
1111
import { asBase64 } from "../../src/event/validation";
1212

1313
const type = "org.cncf.cloudevents.example";
@@ -16,7 +16,7 @@ const id = "b46cf653-d48a-4b90-8dfa-355c01061361";
1616

1717
const fixture = Object.freeze({
1818
id,
19-
specversion: Version.V1,
19+
specversion: V1,
2020
source,
2121
type,
2222
data: `"some data"`
@@ -165,7 +165,7 @@ describe("A 1.0 CloudEvent", () => {
165165
});
166166

167167
it("can be constructed with an ID", () => {
168-
const ce = new CloudEvent({ id: "1234", specversion: Version.V1, source, type });
168+
const ce = new CloudEvent({ id: "1234", specversion: V1, source, type });
169169
expect(ce.id).to.equal("1234");
170170
});
171171

@@ -280,7 +280,7 @@ describe("A 1.0 CloudEvent", () => {
280280
const obj = JSON.parse(json as string);
281281
expect(obj.type).to.equal(type);
282282
expect(obj.source).to.equal(source);
283-
expect(obj.specversion).to.equal(Version.V1);
283+
expect(obj.specversion).to.equal(V1);
284284
});
285285

286286
it("throws if the provded source is empty string", () => {

test/integration/kafka_tests.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import path from "path";
77
import fs from "fs";
88

99
import { expect } from "chai";
10-
import { CloudEvent, CONSTANTS, Version } from "../../src";
10+
import { CloudEvent, CONSTANTS, V1 } from "../../src";
1111
import { asBase64 } from "../../src/event/validation";
1212
import { Message, Kafka, KafkaMessage, KafkaEvent } from "../../src/message";
1313
import { KAFKA_CE_HEADERS } from "../../src/message/kafka/headers";
@@ -43,7 +43,7 @@ const imageData = new Uint32Array(fs.readFileSync(path.join(process.cwd(), "test
4343
const image_base64 = asBase64(imageData);
4444

4545
const fixture = new CloudEvent({
46-
specversion: Version.V1,
46+
specversion: V1,
4747
id,
4848
type,
4949
source,
@@ -233,7 +233,7 @@ describe("Kafka transport", () => {
233233
expect(message.body).to.equal(data);
234234
// validate all headers
235235
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype);
236-
expect(message.headers[KAFKA_CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
236+
expect(message.headers[KAFKA_CE_HEADERS.SPEC_VERSION]).to.equal(V1);
237237
expect(message.headers[KAFKA_CE_HEADERS.ID]).to.equal(id);
238238
expect(message.headers[KAFKA_CE_HEADERS.TYPE]).to.equal(type);
239239
expect(message.headers[KAFKA_CE_HEADERS.SOURCE]).to.equal(source);
@@ -249,7 +249,7 @@ describe("Kafka transport", () => {
249249
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE);
250250
// Parse the message body as JSON, then validate the attributes
251251
const body = JSON.parse(message.body as string);
252-
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1);
252+
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1);
253253
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
254254
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
255255
expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source);

test/integration/message_test.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import fs from "fs";
88

99
import { expect } from "chai";
1010
import { IncomingHttpHeaders } from "http";
11-
import { CloudEvent, CONSTANTS, Version } from "../../src";
11+
import { CloudEvent, CONSTANTS, V1, V03 } from "../../src";
1212
import { asBase64 } from "../../src/event/validation";
1313
import { Message, HTTP } from "../../src/message";
1414

@@ -154,7 +154,7 @@ describe("HTTP transport", () => {
154154
[CONSTANTS.CE_HEADERS.ID]: "1234",
155155
[CONSTANTS.CE_HEADERS.SOURCE]: "test",
156156
[CONSTANTS.CE_HEADERS.TYPE]: "test.event",
157-
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: Version.V1,
157+
[CONSTANTS.CE_HEADERS.SPEC_VERSION]: V1,
158158
"ce-LUNCH": "tacos",
159159
},
160160
};
@@ -237,7 +237,7 @@ describe("HTTP transport", () => {
237237
id,
238238
type,
239239
source,
240-
specversion: Version.V1,
240+
specversion: V1,
241241
data: { lunch: "tacos" },
242242
});
243243
const message: Message<undefined> = {
@@ -250,7 +250,7 @@ describe("HTTP transport", () => {
250250

251251
describe("Specification version V1", () => {
252252
const fixture = new CloudEvent({
253-
specversion: Version.V1,
253+
specversion: V1,
254254
id,
255255
type,
256256
source,
@@ -268,7 +268,7 @@ describe("HTTP transport", () => {
268268
expect(message.body).to.equal(JSON.stringify(data));
269269
// validate all headers
270270
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype);
271-
expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
271+
expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(V1);
272272
expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id);
273273
expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type);
274274
expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source);
@@ -284,7 +284,7 @@ describe("HTTP transport", () => {
284284
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE);
285285
// Parse the message body as JSON, then validate the attributes
286286
const body = JSON.parse(message.body as string);
287-
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1);
287+
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1);
288288
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
289289
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
290290
expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source);
@@ -353,7 +353,7 @@ describe("HTTP transport", () => {
353353

354354
describe("Specification version V03", () => {
355355
const fixture = new CloudEvent({
356-
specversion: Version.V03,
356+
specversion: V03,
357357
id,
358358
type,
359359
source,
@@ -371,7 +371,7 @@ describe("HTTP transport", () => {
371371
expect(message.body).to.equal(JSON.stringify(data));
372372
// validate all headers
373373
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype);
374-
expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03);
374+
expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(V03);
375375
expect(message.headers[CONSTANTS.CE_HEADERS.ID]).to.equal(id);
376376
expect(message.headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(type);
377377
expect(message.headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(source);
@@ -387,7 +387,7 @@ describe("HTTP transport", () => {
387387
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE);
388388
// Parse the message body as JSON, then validate the attributes
389389
const body = JSON.parse(message.body as string);
390-
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03);
390+
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V03);
391391
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
392392
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
393393
expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source);

test/integration/mqtt_tests.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import path from "path";
77
import fs from "fs";
88

99
import { expect } from "chai";
10-
import { CloudEvent, CONSTANTS, Version, Headers } from "../../src";
10+
import { CloudEvent, CONSTANTS, V1, Headers } from "../../src";
1111
import { asBase64 } from "../../src/event/validation";
1212
import { Message, MQTT, MQTTMessage } from "../../src/message";
1313

@@ -43,7 +43,7 @@ const image_base64 = asBase64(imageData);
4343
const PUBLISH = {"Content Type": "application/json; charset=utf-8"};
4444

4545
const fixture = new CloudEvent({
46-
specversion: Version.V1,
46+
specversion: V1,
4747
id,
4848
type,
4949
source,
@@ -216,7 +216,7 @@ describe("MQTT transport", () => {
216216
expect(message.body).to.equal(data);
217217
// validate all headers
218218
expect(message.headers.datacontenttype).to.equal(datacontenttype);
219-
expect(message.headers.specversion).to.equal(Version.V1);
219+
expect(message.headers.specversion).to.equal(V1);
220220
expect(message.headers.id).to.equal(id);
221221
expect(message.headers.type).to.equal(type);
222222
expect(message.headers.source).to.equal(source);
@@ -232,7 +232,7 @@ describe("MQTT transport", () => {
232232
expect(message.body).to.equal(data);
233233
// validate all headers
234234
expect(message["User Properties"]?.datacontenttype).to.equal(datacontenttype);
235-
expect(message["User Properties"]?.specversion).to.equal(Version.V1);
235+
expect(message["User Properties"]?.specversion).to.equal(V1);
236236
expect(message["User Properties"]?.id).to.equal(id);
237237
expect(message["User Properties"]?.type).to.equal(type);
238238
expect(message["User Properties"]?.source).to.equal(source);
@@ -249,7 +249,7 @@ describe("MQTT transport", () => {
249249
expect(message.body).to.deep.equal(message.payload);
250250
expect(message.payload).to.deep.equal(fixture.toJSON());
251251
const body = message.body as Record<string, string>;
252-
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1);
252+
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(V1);
253253
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
254254
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
255255
expect(body[CONSTANTS.CE_ATTRIBUTES.SOURCE]).to.equal(source);

0 commit comments

Comments
 (0)