diff --git a/.changeset/curly-fishes-tease.md b/.changeset/curly-fishes-tease.md new file mode 100644 index 00000000000..492060ae48d --- /dev/null +++ b/.changeset/curly-fishes-tease.md @@ -0,0 +1,5 @@ +--- +"@smithy/core": patch +--- + +fix NumericValue typecheck diff --git a/packages/core/src/submodules/cbor/byte-printer.ts b/packages/core/src/submodules/cbor/byte-printer.ts index e1c73c24c6c..6c4f7be2dcb 100644 --- a/packages/core/src/submodules/cbor/byte-printer.ts +++ b/packages/core/src/submodules/cbor/byte-printer.ts @@ -4,5 +4,42 @@ * @deprecated for testing only, do not use in runtime. */ export function printBytes(bytes: Uint8Array) { - return [...bytes].map((n) => ("0".repeat(8) + n.toString(2)).slice(-8) + ` (${n})`); + return [...bytes].map((n) => { + const pad = (num: number) => ("0".repeat(8) + num.toString(2)).slice(-8); + const b = pad(n); + const [maj, min] = [b.slice(0, 3), b.slice(3)]; + + let dmaj: string = ""; + + switch (maj) { + case "000": + dmaj = "0 - Uint64"; + break; + case "001": + dmaj = "1 - Neg Uint64"; + break; + case "010": + dmaj = "2 - unstructured bytestring"; + break; + case "011": + dmaj = "3 - utf8 string"; + break; + case "100": + dmaj = "4 - list"; + break; + case "101": + dmaj = "5 - map"; + break; + case "110": + dmaj = "6 - tag"; + break; + case "111": + dmaj = "7 - special"; + break; + default: + dmaj = String(parseInt(maj, 2)); + } + + return `${maj}_${min} (${dmaj}, ${parseInt(min, 2)})`; + }); } diff --git a/packages/core/src/submodules/cbor/cbor.spec.ts b/packages/core/src/submodules/cbor/cbor.spec.ts index 89c74a2162c..8bf59268cb8 100644 --- a/packages/core/src/submodules/cbor/cbor.spec.ts +++ b/packages/core/src/submodules/cbor/cbor.spec.ts @@ -5,6 +5,7 @@ import JSONbig from "json-bigint"; import * as path from "path"; import { describe, expect, test as it } from "vitest"; +import { printBytes } from "./byte-printer"; import { cbor } from "./cbor"; import { bytesToFloat16 } from "./cbor-decode"; import { tagSymbol } from "./cbor-types"; @@ -286,7 +287,7 @@ describe("cbor", () => { expect(deserialized).toEqual(bigInt); }); - it.skip("should round-trip NumericValue to major 6 with tag 4", () => { + it("should round-trip NumericValue to major 6 with tag 4", () => { for (const bigDecimal of [ "10000000000000000000000054.321", "1000000000000000000000000000000000054.134134321", @@ -310,6 +311,15 @@ describe("cbor", () => { expect(deserialized).toEqual(nv); expect(deserialized.string).toEqual(nv.string); } + + const bigDecimal = nv("0"); + expect(bigDecimal).toBeInstanceOf(NumericValue); + expect(printBytes(cbor.serialize(bigDecimal))).toEqual([ + "110_00100 (6 - tag, 4)", + "100_00010 (4 - list, 2)", + "000_00000 (0 - Uint64, 0)", + "000_00000 (0 - Uint64, 0)", + ]); }); it("should round-trip sequences of big numbers", () => { diff --git a/packages/core/src/submodules/serde/value/NumericValue.spec.ts b/packages/core/src/submodules/serde/value/NumericValue.spec.ts index 26cbacc4dbd..f790632ba7f 100644 --- a/packages/core/src/submodules/serde/value/NumericValue.spec.ts +++ b/packages/core/src/submodules/serde/value/NumericValue.spec.ts @@ -16,4 +16,59 @@ describe(NumericValue.name, () => { expect(() => nv("-10.1")).not.toThrow(); expect(() => nv("-.101")).not.toThrow(); }); + + it("has a custom instanceof check", () => { + const isInstance = [ + nv("0"), + nv("-0.00"), + new NumericValue("0", "bigDecimal"), + new NumericValue("-0.00", "bigDecimal"), + { + string: "abcd", + type: "bigDecimal", + constructor: { + name: "_NumericValue", + }, + }, + (() => { + const x = {}; + Object.setPrototypeOf(x, NumericValue.prototype); + return x; + })(), + (() => { + function F() {} + F.prototype = Object.create(NumericValue.prototype); + // @ts-ignore + return new F(); + })(), + (() => { + return new (class extends NumericValue {})("0", "bigDecimal"); + })(), + ] as unknown[]; + + const isNotInstance = [ + BigInt(0), + "-0.00", + { + string: "abcd", + type: "bigDecimal", + constructor: { + name: "_NumericValue_", + }, + }, + (() => { + const x = {}; + Object.setPrototypeOf(x, NumericValue); + return x; + })(), + ] as unknown[]; + + for (const instance of isInstance) { + expect(instance).toBeInstanceOf(NumericValue); + } + + for (const instance of isNotInstance) { + expect(instance).not.toBeInstanceOf(NumericValue); + } + }); }); diff --git a/packages/core/src/submodules/serde/value/NumericValue.ts b/packages/core/src/submodules/serde/value/NumericValue.ts index ad3af6e2de5..d3b081ca42a 100644 --- a/packages/core/src/submodules/serde/value/NumericValue.ts +++ b/packages/core/src/submodules/serde/value/NumericValue.ts @@ -58,11 +58,15 @@ export class NumericValue { return false; } const _nv = object as NumericValue; - const prototypeMatch = NumericValue.prototype.isPrototypeOf(object.constructor?.prototype); + const prototypeMatch = NumericValue.prototype.isPrototypeOf(object); if (prototypeMatch) { return prototypeMatch; } - if (typeof _nv.string === "string" && typeof _nv.type === "string" && _nv.constructor?.name === "NumericValue") { + if ( + typeof _nv.string === "string" && + typeof _nv.type === "string" && + _nv.constructor?.name?.endsWith("NumericValue") + ) { return true; } return prototypeMatch;