Skip to content

Commit a39e20d

Browse files
authored
implement option layout item (#530)
1 parent cdd08a9 commit a39e20d

File tree

4 files changed

+92
-3
lines changed

4 files changed

+92
-3
lines changed

core/base/__tests__/layout.ts

+43
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
deserializeLayout,
1111
layoutDiscriminator,
1212
serializeLayout,
13+
optionItem,
1314
} from "./../src/index.js";
1415

1516
// prettier-ignore
@@ -226,6 +227,48 @@ describe("Layout tests", function () {
226227
});
227228
});
228229

230+
describe("OptionItem tests", () => {
231+
it("basic test", () => {
232+
const layout = optionItem({binary: "uint", size: 1});
233+
234+
const testCases = [[32, [1, 32]], [undefined, [0]]] as const;
235+
for (const [data, expected] of testCases) {
236+
const encoded = serializeLayout(layout, data);
237+
expect(encoded).toEqual(new Uint8Array(expected));
238+
239+
const decoded = deserializeLayout(layout, encoded);
240+
expect(decoded).toEqual(data);
241+
}
242+
})
243+
244+
it("advanced test", () => {
245+
const layout = { binary: "array", layout: [
246+
{ name: "firstOption", ...optionItem({binary: "uint", size: 1}) },
247+
{ name: "someUint", binary: "uint", size: 1},
248+
{ name: "secondOption", ...optionItem({binary: "bytes", size: 4}) },
249+
]} as const satisfies Layout;
250+
251+
const data = [
252+
{ firstOption: undefined, someUint: 1, secondOption: undefined},
253+
{ firstOption: 10, someUint: 2, secondOption: undefined },
254+
{ firstOption: undefined, someUint: 3, secondOption: new Uint8Array([1,2,3,4]) },
255+
{ firstOption: 20, someUint: 4, secondOption: new Uint8Array([5,6,7,8]) },
256+
] as const;
257+
const expected = new Uint8Array([
258+
...[0, 1, 0 ],
259+
...[1, 10, 2, 0 ],
260+
...[0, 3, 1, 1, 2, 3, 4],
261+
...[1, 20, 4, 1, 5, 6, 7, 8],
262+
]);
263+
264+
const encoded = serializeLayout(layout, data);
265+
expect(encoded).toEqual(new Uint8Array(expected));
266+
267+
const decoded = deserializeLayout(layout, encoded);
268+
expect(decoded).toEqual(data);
269+
})
270+
})
271+
229272
it("should serialize and deserialize correctly", function () {
230273
const encoded = serializeLayout(testLayout, completeValues);
231274
const decoded = deserializeLayout(testLayout, encoded);

core/base/src/utils/layout/items.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
1-
import type { NumSizeToPrimitive} from './layout.js';
1+
import type { NumSizeToPrimitive, LayoutToType, CustomConversion } from './layout.js';
22
import { numberMaxSize } from './layout.js';
3+
import type { CustomizableBytes, CustomizableBytesReturn } from './utils.js';
4+
import { customizableBytes } from './utils.js';
35

46
//TODO implement enum item
57

8+
const baseOptionItem = <const T extends CustomizableBytes>(someType: T) => ({
9+
binary: "switch",
10+
idSize: 1,
11+
idTag: "isSome",
12+
layouts: [
13+
[[0, false], []],
14+
[[1, true ], [customizableBytes({ name: "value"}, someType)]],
15+
]
16+
} as const);
17+
18+
type BaseOptionItem<T extends CustomizableBytes> =
19+
LayoutToType<ReturnType<typeof baseOptionItem<T>>>;
20+
21+
type BaseOptionValue<T extends CustomizableBytes> =
22+
LayoutToType<CustomizableBytesReturn<{}, T>> | undefined;
23+
24+
export function optionItem<const T extends CustomizableBytes>(optVal: T) {
25+
return {
26+
binary: "bytes",
27+
layout: baseOptionItem(optVal),
28+
custom: {
29+
to: (obj: BaseOptionItem<T>): BaseOptionValue<T> =>
30+
obj.isSome === true
31+
//TODO I'm really not sure why we need to manually narrow the type here
32+
? (obj as Exclude<typeof obj, {isSome: false}>)["value"]
33+
: undefined,
34+
from: (value: BaseOptionValue<T>): BaseOptionItem<T> =>
35+
value === undefined
36+
? { isSome: false }
37+
//TODO and this is even more sketch
38+
: ({ isSome: true, value } as unknown as Exclude<BaseOptionItem<T>, { isSome: false }>),
39+
} satisfies CustomConversion<BaseOptionItem<T>, BaseOptionValue<T>>
40+
} as const
41+
};
42+
643
export type Bitset<B extends readonly (string | undefined)[]> =
744
{[K in B[number] as K extends "" | undefined ? never : K]: boolean};
845

core/base/src/utils/layout/size.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ function calcItemSize(item: LayoutItem, data: any): number | null {
2121
const lengthSize = ("lengthSize" in item) ? item.lengthSize | 0 : 0;
2222

2323
if ("layout" in item) {
24-
const layoutSize = internalCalcLayoutSize(item.layout, data);
24+
const { custom } = item;
25+
const layoutSize = internalCalcLayoutSize(
26+
item.layout,
27+
custom === undefined
28+
? data
29+
: typeof custom.from === "function"
30+
? custom.from(data)
31+
: custom.from
32+
);
2533
if (layoutSize === null)
2634
return ("size" in item ) ? item.size ?? null : null;
2735

core/base/src/utils/layout/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ type CombineObjects<T, U> = {
5454
};
5555

5656
export type BytesBase =
57-
{ readonly name: string } & Omit<BytesLayoutItem, "binary" | "custom" | "layout">;
57+
( {} | { readonly name: string } ) & Omit<BytesLayoutItem, "binary" | "custom" | "layout">;
58+
5859

5960
export type CustomizableBytesReturn<B extends BytesBase, P extends CustomizableBytes> =
6061
CombineObjects<

0 commit comments

Comments
 (0)