Skip to content

Commit db40de9

Browse files
committed
fix: do not alter an event's data attribute
When setting an event's data attribute we were trying to be really clever and this is problematic. Instead, keep the data attribute unchanged. Per the 1.0 specification, the data attribute is still inspected to determine if it is binary, and if so, a data_base64 attribute is added with the contents of the data property encoded as base64. Fixes: #343 Signed-off-by: Lance Ball <[email protected]>
1 parent e334b6e commit db40de9

File tree

7 files changed

+41
-51
lines changed

7 files changed

+41
-51
lines changed

src/event/cloudevent.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import {
1010
} from "./interfaces";
1111
import { validateCloudEvent } from "./spec";
1212
import { ValidationError, isBinary, asBase64, isValidType } from "./validation";
13-
import CONSTANTS from "../constants";
14-
import { isString } from "util";
1513

1614
/**
1715
* An enum representing the CloudEvent specification version
@@ -92,7 +90,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
9290
this.schemaurl = properties.schemaurl as string;
9391
delete properties.schemaurl;
9492

95-
this._setData(properties.data);
93+
this.data = properties.data;
9694
delete properties.data;
9795

9896
// sanity checking
@@ -125,25 +123,11 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
125123
}
126124

127125
get data(): unknown {
128-
if (
129-
this.datacontenttype === CONSTANTS.MIME_JSON &&
130-
!(this.datacontentencoding === CONSTANTS.ENCODING_BASE64) &&
131-
isString(this.#_data)
132-
) {
133-
return JSON.parse(this.#_data as string);
134-
} else if (isBinary(this.#_data)) {
135-
return asBase64(this.#_data as Uint32Array);
136-
}
137126
return this.#_data;
138127
}
139128

140129
set data(value: unknown) {
141-
this._setData(value);
142-
}
143-
144-
private _setData(value: unknown): void {
145130
if (isBinary(value)) {
146-
this.#_data = value;
147131
this.data_base64 = asBase64(value as Uint32Array);
148132
}
149133
this.#_data = value;
@@ -158,7 +142,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
158142
toJSON(): Record<string, unknown> {
159143
const event = { ...this };
160144
event.time = new Date(this.time as string).toISOString();
161-
event.data = this.data;
145+
event.data = !isBinary(this.data) ? this.data : undefined;
162146
return event;
163147
}
164148

src/message/http/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import { CloudEvent, CloudEventV03, CloudEventV1, CONSTANTS, Mode, Version } fro
22
import { Message, Headers } from "..";
33

44
import { headersFor, sanitize, v03structuredParsers, v1binaryParsers, v1structuredParsers } from "./headers";
5-
import { asData, isBase64, isString, isStringOrObjectOrThrow, ValidationError } from "../../event/validation";
5+
import { isBase64, isString, isStringOrObjectOrThrow, ValidationError } from "../../event/validation";
66
import { Base64Parser, JSONParser, MappedParser, Parser, parserByContentType } from "../../parsers";
77

88
// implements Serializer
99
export function binary(event: CloudEvent): Message {
1010
const contentType: Headers = { [CONSTANTS.HEADER_CONTENT_TYPE]: CONSTANTS.DEFAULT_CONTENT_TYPE };
1111
const headers: Headers = { ...contentType, ...headersFor(event) };
12-
let body = asData(event.data, event.datacontenttype as string);
13-
if (typeof body === "object") {
14-
body = JSON.stringify(body);
12+
let body = event.data;
13+
if (typeof event.data === "object" && !(event.data instanceof Uint32Array)) {
14+
// we'll stringify objects, but not binary data
15+
body = JSON.stringify(event.data);
1516
}
1617
return {
1718
headers,
@@ -89,7 +90,7 @@ function getMode(headers: Headers): Mode {
8990
* @param {Record<string, unknown>} body the HTTP request body
9091
* @returns {Version} the CloudEvent specification version
9192
*/
92-
function getVersion(mode: Mode, headers: Headers, body: string | Record<string, string>) {
93+
function getVersion(mode: Mode, headers: Headers, body: string | Record<string, string> | unknown) {
9394
if (mode === Mode.BINARY) {
9495
// Check the headers for the version
9596
const versionHeader = headers[CONSTANTS.CE_HEADERS.SPEC_VERSION];
@@ -149,10 +150,12 @@ function parseBinary(message: Message, version: Version): CloudEvent {
149150

150151
if (body) {
151152
const parser = parserByContentType[eventObj.datacontenttype as string];
152-
if (!parser) {
153-
throw new ValidationError(`no parser found for content type ${eventObj.datacontenttype}`);
153+
if (parser) {
154+
parsedPayload = parser.parse(body as string);
155+
} else {
156+
// Just use the raw body
157+
parsedPayload = body;
154158
}
155-
parsedPayload = parser.parse(body);
156159
}
157160

158161
// Every unprocessed header can be an extension
@@ -201,7 +204,7 @@ function parseStructured(message: Message, version: Version): CloudEvent {
201204
const contentType = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE];
202205
const parser: Parser = contentType ? parserByContentType[contentType] : new JSONParser();
203206
if (!parser) throw new ValidationError(`invalid content type ${sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]}`);
204-
const incoming = { ...(parser.parse(payload) as Record<string, unknown>) };
207+
const incoming = { ...(parser.parse(payload as string) as Record<string, unknown>) };
205208

206209
const eventObj: { [key: string]: unknown } = {};
207210
const parserMap: Record<string, MappedParser> = version === Version.V1 ? v1structuredParsers : v03structuredParsers;
@@ -221,9 +224,12 @@ function parseStructured(message: Message, version: Version): CloudEvent {
221224
}
222225

223226
// ensure data content is correctly decoded
224-
if (eventObj.data_base64) {
227+
if (eventObj.data_base64 || eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64) {
225228
const parser = new Base64Parser();
226-
eventObj.data = JSON.parse(parser.parse(eventObj.data_base64 as string));
229+
// data_base64 is a property that only exists on V1 events. For V03 events,
230+
// there will be a .datacontentencoding property, and the .data property
231+
// itself will be encoded as base64
232+
eventObj.data = JSON.parse(parser.parse((eventObj.data_base64 as string) || (eventObj.data as string)));
227233
delete eventObj.data_base64;
228234
delete eventObj.datacontentencoding;
229235
}

src/message/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface Headers {
2828
*/
2929
export interface Message {
3030
headers: Headers;
31-
body: string;
31+
body: string | unknown;
3232
}
3333

3434
/**

test/integration/emitter_factory_test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ function superagentEmitter(message: Message, options?: Options): Promise<unknown
5050
for (const key of Object.getOwnPropertyNames(message.headers)) {
5151
post.set(key, message.headers[key]);
5252
}
53-
return post.send(message.body);
53+
return post.send(message.body as string);
5454
}
5555

5656
function gotEmitter(message: Message, options?: Options): Promise<unknown> {
5757
return Promise.resolve(
58-
got.post(sink, { headers: message.headers, body: message.body, ...((options as unknown) as Options) }),
58+
got.post(sink, { headers: message.headers, body: message.body as string, ...((options as unknown) as Options) }),
5959
);
6060
}
6161

test/integration/message_test.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe("HTTP transport", () => {
102102

103103
it("Binary Messages can be created from a CloudEvent", () => {
104104
const message: Message = HTTP.binary(fixture);
105-
expect(JSON.parse(message.body)).to.deep.equal(data);
105+
expect(message.body).to.equal(JSON.stringify(data));
106106
// validate all headers
107107
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(datacontenttype);
108108
expect(message.headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
@@ -120,7 +120,7 @@ describe("HTTP transport", () => {
120120
const message: Message = HTTP.structured(fixture);
121121
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE);
122122
// Parse the message body as JSON, then validate the attributes
123-
const body = JSON.parse(message.body);
123+
const body = JSON.parse(message.body as string);
124124
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1);
125125
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
126126
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
@@ -147,6 +147,7 @@ describe("HTTP transport", () => {
147147
it("Supports Base-64 encoded data in structured messages", () => {
148148
const event = fixture.cloneWith({ data: dataBinary });
149149
expect(event.data_base64).to.equal(data_base64);
150+
expect(event.data).to.equal(dataBinary);
150151
const message = HTTP.structured(event);
151152
const eventDeserialized = HTTP.toEvent(message);
152153
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
@@ -155,9 +156,11 @@ describe("HTTP transport", () => {
155156
it("Supports Base-64 encoded data in binary messages", () => {
156157
const event = fixture.cloneWith({ data: dataBinary });
157158
expect(event.data_base64).to.equal(data_base64);
159+
expect(event.data).to.equal(dataBinary);
158160
const message = HTTP.binary(event);
161+
expect(message.body).to.equal(dataBinary);
159162
const eventDeserialized = HTTP.toEvent(message);
160-
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
163+
expect(eventDeserialized.data).to.equal(dataBinary);
161164
});
162165
});
163166

@@ -196,7 +199,7 @@ describe("HTTP transport", () => {
196199
const message: Message = HTTP.structured(fixture);
197200
expect(message.headers[CONSTANTS.HEADER_CONTENT_TYPE]).to.equal(CONSTANTS.DEFAULT_CE_CONTENT_TYPE);
198201
// Parse the message body as JSON, then validate the attributes
199-
const body = JSON.parse(message.body);
202+
const body = JSON.parse(message.body as string);
200203
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V03);
201204
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
202205
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
@@ -221,19 +224,27 @@ describe("HTTP transport", () => {
221224
});
222225

223226
it("Supports Base-64 encoded data in structured messages", () => {
224-
const event = fixture.cloneWith({ data: dataBinary, datacontentencoding });
225-
expect(event.data_base64).to.equal(data_base64);
227+
const event = fixture.cloneWith({ data: data_base64, datacontentencoding });
226228
const message = HTTP.structured(event);
229+
expect(JSON.parse(message.body as string).data).to.equal(data_base64);
230+
// An incoming event with datacontentencoding set to base64,
231+
// and encoded data, should decode the data before setting
232+
// the .data property on the event
227233
const eventDeserialized = HTTP.toEvent(message);
228234
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
235+
expect(eventDeserialized.datacontentencoding).to.be.undefined;
229236
});
230237

231238
it("Supports Base-64 encoded data in binary messages", () => {
232-
const event = fixture.cloneWith({ data: dataBinary, datacontentencoding });
233-
expect(event.data_base64).to.equal(data_base64);
239+
const event = fixture.cloneWith({ data: data_base64, datacontentencoding });
234240
const message = HTTP.binary(event);
241+
expect(message.body).to.equal(data_base64);
242+
// An incoming event with datacontentencoding set to base64,
243+
// and encoded data, should decode the data before setting
244+
// the .data property on the event
235245
const eventDeserialized = HTTP.toEvent(message);
236246
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
247+
expect(eventDeserialized.datacontentencoding).to.be.undefined;
237248
});
238249
});
239250
});

test/integration/spec_03_tests.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,6 @@ describe("CloudEvents Spec v0.3", () => {
168168

169169
expect(typeof cloudevent.data).to.equal("string");
170170
});
171-
172-
it("should convert data with stringified json to a json object", () => {
173-
cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON });
174-
cloudevent.data = JSON.stringify(data);
175-
expect(cloudevent.data).to.deep.equal(data);
176-
});
177171
});
178172

179173
describe("'subject'", () => {

test/integration/spec_1_tests.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,6 @@ describe("CloudEvents Spec v1.0", () => {
168168
cloudevent = cloudevent.cloneWith({ datacontenttype: dct });
169169
});
170170

171-
it("should convert data with stringified json to a json object", () => {
172-
cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON, data: JSON.stringify(data) });
173-
expect(cloudevent.data).to.deep.equal(data);
174-
});
175-
176171
it("should be ok when type is 'Uint32Array' for 'Binary'", () => {
177172
const dataString = ")(*~^my data for ce#@#$%";
178173

0 commit comments

Comments
 (0)