Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not alter an event's data attribute #344

Merged
merged 4 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 2 additions & 18 deletions src/event/cloudevent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import {
} from "./interfaces";
import { validateCloudEvent } from "./spec";
import { ValidationError, isBinary, asBase64, isValidType } from "./validation";
import CONSTANTS from "../constants";
import { isString } from "util";

/**
* An enum representing the CloudEvent specification version
Expand Down Expand Up @@ -92,7 +90,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
this.schemaurl = properties.schemaurl as string;
delete properties.schemaurl;

this._setData(properties.data);
this.data = properties.data;
delete properties.data;

// sanity checking
Expand Down Expand Up @@ -125,25 +123,11 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
}

get data(): unknown {
if (
this.datacontenttype === CONSTANTS.MIME_JSON &&
!(this.datacontentencoding === CONSTANTS.ENCODING_BASE64) &&
isString(this.#_data)
) {
return JSON.parse(this.#_data as string);
} else if (isBinary(this.#_data)) {
return asBase64(this.#_data as Uint32Array);
}
return this.#_data;
}

set data(value: unknown) {
this._setData(value);
}

private _setData(value: unknown): void {
if (isBinary(value)) {
this.#_data = value;
this.data_base64 = asBase64(value as Uint32Array);
}
this.#_data = value;
Expand All @@ -158,7 +142,7 @@ export class CloudEvent implements CloudEventV1, CloudEventV03 {
toJSON(): Record<string, unknown> {
const event = { ...this };
event.time = new Date(this.time as string).toISOString();
event.data = this.data;
event.data = !isBinary(this.data) ? this.data : undefined;
return event;
}

Expand Down
28 changes: 17 additions & 11 deletions src/message/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { CloudEvent, CloudEventV03, CloudEventV1, CONSTANTS, Mode, Version } fro
import { Message, Headers } from "..";

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

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

if (body) {
const parser = parserByContentType[eventObj.datacontenttype as string];
if (!parser) {
throw new ValidationError(`no parser found for content type ${eventObj.datacontenttype}`);
if (parser) {
parsedPayload = parser.parse(body as string);
} else {
// Just use the raw body
parsedPayload = body;
}
parsedPayload = parser.parse(body);
}

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

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

// ensure data content is correctly decoded
if (eventObj.data_base64) {
if (eventObj.data_base64 || eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64) {
const parser = new Base64Parser();
eventObj.data = JSON.parse(parser.parse(eventObj.data_base64 as string));
// data_base64 is a property that only exists on V1 events. For V03 events,
// there will be a .datacontentencoding property, and the .data property
// itself will be encoded as base64
eventObj.data = JSON.parse(parser.parse((eventObj.data_base64 as string) || (eventObj.data as string)));
delete eventObj.data_base64;
delete eventObj.datacontentencoding;
}
Expand Down
2 changes: 1 addition & 1 deletion src/message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface Headers {
*/
export interface Message {
headers: Headers;
body: string;
body: string | unknown;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions test/integration/emitter_factory_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ function superagentEmitter(message: Message, options?: Options): Promise<unknown
for (const key of Object.getOwnPropertyNames(message.headers)) {
post.set(key, message.headers[key]);
}
return post.send(message.body);
return post.send(message.body as string);
}

function gotEmitter(message: Message, options?: Options): Promise<unknown> {
return Promise.resolve(
got.post(sink, { headers: message.headers, body: message.body, ...((options as unknown) as Options) }),
got.post(sink, { headers: message.headers, body: message.body as string, ...((options as unknown) as Options) }),
);
}

Expand Down
27 changes: 19 additions & 8 deletions test/integration/message_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe("HTTP transport", () => {

it("Binary Messages can be created from a CloudEvent", () => {
const message: Message = HTTP.binary(fixture);
expect(JSON.parse(message.body)).to.deep.equal(data);
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.V1);
Expand All @@ -120,7 +120,7 @@ describe("HTTP transport", () => {
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);
const body = JSON.parse(message.body as string);
expect(body[CONSTANTS.CE_ATTRIBUTES.SPEC_VERSION]).to.equal(Version.V1);
expect(body[CONSTANTS.CE_ATTRIBUTES.ID]).to.equal(id);
expect(body[CONSTANTS.CE_ATTRIBUTES.TYPE]).to.equal(type);
Expand All @@ -147,6 +147,7 @@ describe("HTTP transport", () => {
it("Supports Base-64 encoded data in structured messages", () => {
const event = fixture.cloneWith({ data: dataBinary });
expect(event.data_base64).to.equal(data_base64);
expect(event.data).to.equal(dataBinary);
const message = HTTP.structured(event);
const eventDeserialized = HTTP.toEvent(message);
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
Expand All @@ -155,9 +156,11 @@ describe("HTTP transport", () => {
it("Supports Base-64 encoded data in binary messages", () => {
const event = fixture.cloneWith({ data: dataBinary });
expect(event.data_base64).to.equal(data_base64);
expect(event.data).to.equal(dataBinary);
const message = HTTP.binary(event);
expect(message.body).to.equal(dataBinary);
const eventDeserialized = HTTP.toEvent(message);
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
expect(eventDeserialized.data).to.equal(dataBinary);
});
});

Expand Down Expand Up @@ -196,7 +199,7 @@ describe("HTTP transport", () => {
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);
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);
Expand All @@ -221,19 +224,27 @@ describe("HTTP transport", () => {
});

it("Supports Base-64 encoded data in structured messages", () => {
const event = fixture.cloneWith({ data: dataBinary, datacontentencoding });
expect(event.data_base64).to.equal(data_base64);
const event = fixture.cloneWith({ data: data_base64, datacontentencoding });
const message = HTTP.structured(event);
expect(JSON.parse(message.body as string).data).to.equal(data_base64);
// An incoming event with datacontentencoding set to base64,
// and encoded data, should decode the data before setting
// the .data property on the event
const eventDeserialized = HTTP.toEvent(message);
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
expect(eventDeserialized.datacontentencoding).to.be.undefined;
});

it("Supports Base-64 encoded data in binary messages", () => {
const event = fixture.cloneWith({ data: dataBinary, datacontentencoding });
expect(event.data_base64).to.equal(data_base64);
const event = fixture.cloneWith({ data: data_base64, datacontentencoding });
const message = HTTP.binary(event);
expect(message.body).to.equal(data_base64);
// An incoming event with datacontentencoding set to base64,
// and encoded data, should decode the data before setting
// the .data property on the event
const eventDeserialized = HTTP.toEvent(message);
expect(eventDeserialized.data).to.deep.equal({ foo: "bar" });
expect(eventDeserialized.datacontentencoding).to.be.undefined;
});
});
});
6 changes: 0 additions & 6 deletions test/integration/spec_03_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,6 @@ describe("CloudEvents Spec v0.3", () => {

expect(typeof cloudevent.data).to.equal("string");
});

it("should convert data with stringified json to a json object", () => {
cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON });
cloudevent.data = JSON.stringify(data);
expect(cloudevent.data).to.deep.equal(data);
});
});

describe("'subject'", () => {
Expand Down
5 changes: 0 additions & 5 deletions test/integration/spec_1_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,6 @@ describe("CloudEvents Spec v1.0", () => {
cloudevent = cloudevent.cloneWith({ datacontenttype: dct });
});

it("should convert data with stringified json to a json object", () => {
cloudevent = cloudevent.cloneWith({ datacontenttype: Constants.MIME_JSON, data: JSON.stringify(data) });
expect(cloudevent.data).to.deep.equal(data);
});

it("should be ok when type is 'Uint32Array' for 'Binary'", () => {
const dataString = ")(*~^my data for ce#@#$%";

Expand Down