Skip to content

Commit c73596e

Browse files
committed
fix(core/cbor): NumericValue typecheck
1 parent 17b29f2 commit c73596e

File tree

5 files changed

+116
-8
lines changed

5 files changed

+116
-8
lines changed

.changeset/curly-fishes-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/core": patch
3+
---
4+
5+
fix NumericValue typecheck

packages/core/src/submodules/cbor/byte-printer.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,42 @@
44
* @deprecated for testing only, do not use in runtime.
55
*/
66
export function printBytes(bytes: Uint8Array) {
7-
return [...bytes].map((n) => ("0".repeat(8) + n.toString(2)).slice(-8) + ` (${n})`);
7+
return [...bytes].map((n) => {
8+
const pad = (n) => ("0".repeat(8) + n.toString(2)).slice(-8);
9+
const b = pad(n);
10+
const [maj, min] = [b.slice(0, 3), b.slice(3)];
11+
12+
let dmaj;
13+
14+
switch (maj) {
15+
case "000":
16+
dmaj = "0 - Uint64";
17+
break;
18+
case "001":
19+
dmaj = "1 - Neg Uint64";
20+
break;
21+
case "010":
22+
dmaj = "2 - unstructured bytestring";
23+
break;
24+
case "011":
25+
dmaj = "3 - utf8 string";
26+
break;
27+
case "100":
28+
dmaj = "4 - list";
29+
break;
30+
case "101":
31+
dmaj = "5 - map";
32+
break;
33+
case "110":
34+
dmaj = "6 - tag";
35+
break;
36+
case "111":
37+
dmaj = "7 - special";
38+
break;
39+
default:
40+
dmaj = parseInt(maj, 2);
41+
}
42+
43+
return `${maj}_${min} (${dmaj}, ${parseInt(min, 2)})`;
44+
});
845
}

packages/core/src/submodules/cbor/cbor.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { cbor } from "./cbor";
99
import { bytesToFloat16 } from "./cbor-decode";
1010
import { tagSymbol } from "./cbor-types";
1111
import { dateToTag } from "./parseCborBody";
12+
import { printBytes } from "./byte-printer";
1213

1314
// syntax is ESM but the test target is CJS.
1415
const here = __dirname;
@@ -286,7 +287,7 @@ describe("cbor", () => {
286287
expect(deserialized).toEqual(bigInt);
287288
});
288289

289-
it.skip("should round-trip NumericValue to major 6 with tag 4", () => {
290+
it("should round-trip NumericValue to major 6 with tag 4", () => {
290291
for (const bigDecimal of [
291292
"10000000000000000000000054.321",
292293
"1000000000000000000000000000000000054.134134321",
@@ -310,6 +311,15 @@ describe("cbor", () => {
310311
expect(deserialized).toEqual(nv);
311312
expect(deserialized.string).toEqual(nv.string);
312313
}
314+
315+
const bigDecimal = nv("0");
316+
expect(bigDecimal).toBeInstanceOf(NumericValue);
317+
expect(printBytes(cbor.serialize(bigDecimal))).toEqual([
318+
"110_00100 (6 - tag, 4)",
319+
"100_00010 (4 - list, 2)",
320+
"000_00000 (0 - Uint64, 0)",
321+
"000_00000 (0 - Uint64, 0)",
322+
]);
313323
});
314324

315325
it("should round-trip sequences of big numbers", () => {

packages/core/src/submodules/serde/value/NumericValue.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,59 @@ describe(NumericValue.name, () => {
1616
expect(() => nv("-10.1")).not.toThrow();
1717
expect(() => nv("-.101")).not.toThrow();
1818
});
19+
20+
it("has a custom instanceof check", () => {
21+
const isInstance = [
22+
nv("0"),
23+
nv("-0.00"),
24+
new NumericValue("0", "bigDecimal"),
25+
new NumericValue("-0.00", "bigDecimal"),
26+
{
27+
string: "abcd",
28+
type: "bigDecimal",
29+
constructor: {
30+
name: "_NumericValue",
31+
},
32+
},
33+
(() => {
34+
const x = {};
35+
Object.setPrototypeOf(x, NumericValue.prototype);
36+
return x;
37+
})(),
38+
(() => {
39+
function F() {}
40+
F.prototype = Object.create(NumericValue.prototype);
41+
// @ts-ignore
42+
return new F();
43+
})(),
44+
(() => {
45+
return new (class extends NumericValue {})("0", "bigDecimal");
46+
})(),
47+
] as unknown[];
48+
49+
const isNotInstance = [
50+
BigInt(0),
51+
"-0.00",
52+
{
53+
string: "abcd",
54+
type: "bigDecimal",
55+
constructor: {
56+
name: "_NumericValue_",
57+
},
58+
},
59+
(() => {
60+
const x = {};
61+
Object.setPrototypeOf(x, NumericValue);
62+
return x;
63+
})(),
64+
] as unknown[];
65+
66+
for (const instance of isInstance) {
67+
expect(instance).toBeInstanceOf(NumericValue);
68+
}
69+
70+
for (const instance of isNotInstance) {
71+
expect(instance).not.toBeInstanceOf(NumericValue);
72+
}
73+
});
1974
});

packages/core/src/submodules/serde/value/NumericValue.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ export type NumericType = "bigDecimal";
2121
* @public
2222
*/
2323
export class NumericValue {
24-
public constructor(
25-
public readonly string: string,
26-
public readonly type: NumericType
27-
) {
24+
public constructor(public readonly string: string, public readonly type: NumericType) {
2825
let dot = 0;
2926
for (let i = 0; i < string.length; ++i) {
3027
const char = string.charCodeAt(i);
@@ -58,11 +55,15 @@ export class NumericValue {
5855
return false;
5956
}
6057
const _nv = object as NumericValue;
61-
const prototypeMatch = NumericValue.prototype.isPrototypeOf(object.constructor?.prototype);
58+
const prototypeMatch = NumericValue.prototype.isPrototypeOf(object);
6259
if (prototypeMatch) {
6360
return prototypeMatch;
6461
}
65-
if (typeof _nv.string === "string" && typeof _nv.type === "string" && _nv.constructor?.name === "NumericValue") {
62+
if (
63+
typeof _nv.string === "string" &&
64+
typeof _nv.type === "string" &&
65+
_nv.constructor?.name?.endsWith("NumericValue")
66+
) {
6667
return true;
6768
}
6869
return prototypeMatch;

0 commit comments

Comments
 (0)