From f869a69e6c74db6f4ea2e245ad42a4eb733af9a9 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Tue, 14 Jun 2022 15:27:34 -0400 Subject: [PATCH 1/2] fix: allow `Uint16|8Array` for binary data Previously we only considered `Uint32Array` binary data. This was an oversight. This fixes that issue. Fixes: https://github.com/cloudevents/sdk-javascript/issues/491 Signed-off-by: Lance Ball --- src/event/cloudevent.ts | 2 +- src/event/validation.ts | 10 +++++++--- test/integration/spec_1_tests.ts | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/event/cloudevent.ts b/src/event/cloudevent.ts index 8c67baa3..a8e25807 100644 --- a/src/event/cloudevent.ts +++ b/src/event/cloudevent.ts @@ -133,7 +133,7 @@ See: https://github.com/cloudevents/spec/blob/v1.0/spec.md#type-system`); set data(value: T | undefined) { if (isBinary(value)) { - this.data_base64 = asBase64(value); + this.data_base64 = asBase64(value as unknown as Buffer); } this.#_data = value; } diff --git a/src/event/validation.ts b/src/event/validation.ts index 0b6fbed7..04257476 100644 --- a/src/event/validation.ts +++ b/src/event/validation.ts @@ -36,7 +36,10 @@ export const isDefined = (v: unknown): boolean => v !== null && typeof v !== "un export const isBoolean = (v: unknown): boolean => typeof v === "boolean"; export const isInteger = (v: unknown): boolean => Number.isInteger(v as number); export const isDate = (v: unknown): v is Date => v instanceof Date; -export const isBinary = (v: unknown): v is Uint32Array => v instanceof Uint32Array; +export const isBinary = (v: unknown): boolean => + v instanceof Uint32Array || + v instanceof Uint16Array || + v instanceof Uint8Array; export const isStringOrThrow = (v: unknown, t: Error): boolean => isString(v) @@ -73,7 +76,7 @@ export const isBase64 = (value: unknown): boolean => export const isBuffer = (value: unknown): boolean => value instanceof Buffer; -export const asBuffer = (value: string | Buffer | Uint32Array): Buffer => +export const asBuffer = (value: string | Buffer | Uint32Array | Uint16Array | Uint8Array): Buffer => isBinary(value) ? Buffer.from((value as unknown) as string) : isBuffer(value) @@ -82,7 +85,8 @@ export const asBuffer = (value: string | Buffer | Uint32Array): Buffer => throw new TypeError("is not buffer or a valid binary"); })(); -export const asBase64 = (value: string | Buffer | Uint32Array): string => asBuffer(value).toString("base64"); +export const asBase64 = +(value: string | Buffer | Uint32Array | Uint16Array | Uint8Array): string => asBuffer(value).toString("base64"); export const clone = (o: Record): Record => JSON.parse(JSON.stringify(o)); diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index e2bbd625..14a9e293 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -182,5 +182,25 @@ describe("CloudEvents Spec v1.0", () => { const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); expect(ce.data_base64).to.equal(expected); }); + + it("should be ok when type is 'Uint16Array' for 'Binary'", () => { + const dataString = ")(*~^my data for ce#@#$%"; + + const dataBinary = Uint16Array.from(dataString, (c) => c.codePointAt(0) as number); + const expected = asBase64(dataBinary); + + const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); + expect(ce.data_base64).to.equal(expected); + }); + + it("should be ok when type is 'Uint8Array' for 'Binary'", () => { + const dataString = ")(*~^my data for ce#@#$%"; + + const dataBinary = Uint8Array.from(dataString, (c) => c.codePointAt(0) as number); + const expected = asBase64(dataBinary); + + const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); + expect(ce.data_base64).to.equal(expected); + }); }); }); From d16084c5141990c9322bc6c65cc97e5cdac50104 Mon Sep 17 00:00:00 2001 From: Lance Ball Date: Tue, 14 Jun 2022 17:28:43 -0400 Subject: [PATCH 2/2] fixup: review feedback Signed-off-by: Lance Ball --- src/event/validation.ts | 15 +++--- test/integration/spec_1_tests.ts | 82 +++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/event/validation.ts b/src/event/validation.ts index 04257476..1926b246 100644 --- a/src/event/validation.ts +++ b/src/event/validation.ts @@ -5,6 +5,10 @@ import { ErrorObject } from "ajv"; +export type TypeArray = Int8Array | Uint8Array | Int16Array | Uint16Array | + Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array; + + /** * An Error class that will be thrown when a CloudEvent * cannot be properly validated against a specification. @@ -36,10 +40,7 @@ export const isDefined = (v: unknown): boolean => v !== null && typeof v !== "un export const isBoolean = (v: unknown): boolean => typeof v === "boolean"; export const isInteger = (v: unknown): boolean => Number.isInteger(v as number); export const isDate = (v: unknown): v is Date => v instanceof Date; -export const isBinary = (v: unknown): boolean => - v instanceof Uint32Array || - v instanceof Uint16Array || - v instanceof Uint8Array; +export const isBinary = (v: unknown): boolean => ArrayBuffer.isView(v); export const isStringOrThrow = (v: unknown, t: Error): boolean => isString(v) @@ -76,7 +77,7 @@ export const isBase64 = (value: unknown): boolean => export const isBuffer = (value: unknown): boolean => value instanceof Buffer; -export const asBuffer = (value: string | Buffer | Uint32Array | Uint16Array | Uint8Array): Buffer => +export const asBuffer = (value: string | Buffer | TypeArray): Buffer => isBinary(value) ? Buffer.from((value as unknown) as string) : isBuffer(value) @@ -86,7 +87,7 @@ export const asBuffer = (value: string | Buffer | Uint32Array | Uint16Array | Ui })(); export const asBase64 = -(value: string | Buffer | Uint32Array | Uint16Array | Uint8Array): string => asBuffer(value).toString("base64"); +(value: string | Buffer | TypeArray): string => asBuffer(value).toString("base64"); export const clone = (o: Record): Record => JSON.parse(JSON.stringify(o)); @@ -101,5 +102,5 @@ export const asData = (data: unknown, contentType: string): string => { return isBinary(maybeJson) ? asBase64(maybeJson) : maybeJson; }; -export const isValidType = (v: boolean | number | string | Date | Uint32Array | unknown): boolean => +export const isValidType = (v: boolean | number | string | Date | TypeArray | unknown): boolean => isBoolean(v) || isInteger(v) || isString(v) || isDate(v) || isBinary(v) || isObject(v); diff --git a/test/integration/spec_1_tests.ts b/test/integration/spec_1_tests.ts index 14a9e293..7bd22483 100644 --- a/test/integration/spec_1_tests.ts +++ b/test/integration/spec_1_tests.ts @@ -173,34 +173,60 @@ describe("CloudEvents Spec v1.0", () => { expect(typeof ce.data).to.equal("string"); }); - it("should be ok when type is 'Uint32Array' for 'Binary'", () => { - const dataString = ")(*~^my data for ce#@#$%"; - - const dataBinary = Uint32Array.from(dataString, (c) => c.codePointAt(0) as number); - const expected = asBase64(dataBinary); - - const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); - expect(ce.data_base64).to.equal(expected); - }); - - it("should be ok when type is 'Uint16Array' for 'Binary'", () => { - const dataString = ")(*~^my data for ce#@#$%"; - - const dataBinary = Uint16Array.from(dataString, (c) => c.codePointAt(0) as number); - const expected = asBase64(dataBinary); - - const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); - expect(ce.data_base64).to.equal(expected); - }); - - it("should be ok when type is 'Uint8Array' for 'Binary'", () => { - const dataString = ")(*~^my data for ce#@#$%"; - - const dataBinary = Uint8Array.from(dataString, (c) => c.codePointAt(0) as number); - const expected = asBase64(dataBinary); - - const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: dataBinary }); - expect(ce.data_base64).to.equal(expected); + const dataString = ")(*~^my data for ce#@#$%"; + const testCases = [ + { + type: Int8Array, + data: Int8Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Int8Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Uint8Array, + data: Uint8Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Uint8Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Int16Array, + data: Int16Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Int16Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Uint16Array, + data: Uint16Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Uint16Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Int32Array, + data: Int32Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Int32Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Uint32Array, + data: Uint32Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Uint32Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Uint8ClampedArray, + data: Uint8ClampedArray.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Uint8ClampedArray.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Float32Array, + data: Float32Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Float32Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + { + type: Float64Array, + data: Float64Array.from(dataString, (c) => c.codePointAt(0) as number), + expected: asBase64(Float64Array.from(dataString, (c) => c.codePointAt(0) as number)) + }, + ]; + + testCases.forEach((test) => { + it(`should be ok when type is '${test.type.name}' for 'Binary'`, () => { + const ce = cloudevent.cloneWith({ datacontenttype: "text/plain", data: test.data }); + expect(ce.data_base64).to.equal(test.expected); + }); }); }); });