Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
fix: disambiguate serialization output of builtins
Browse files Browse the repository at this point in the history
  • Loading branch information
helmturner committed Jan 4, 2024
1 parent f367de1 commit 9aa3dc1
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 72 deletions.
60 changes: 34 additions & 26 deletions src/async/asyncTypes2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,38 @@ import {
createTsonAsyncUnfoldFn,
} from "./createUnfoldAsyncFn.js";

export const ChunkTypes = {
BODY: "BODY",
ERROR: "ERROR",
HEAD: "HEAD",
LEAF: "LEAF",
REF: "REF",
TAIL: "TAIL",
} as const;

export type ChunkTypes = {
[key in keyof typeof ChunkTypes]: (typeof ChunkTypes)[key];
};

export const TsonStatus = {
//MULTI_STATUS: 207,
ERROR: 500,
INCOMPLETE: 203,
OK: 200,
} as const;

export type TsonStatus = {
[key in keyof typeof TsonStatus]: (typeof TsonStatus)[key];
};

export const TsonStructures = {
ARRAY: 0,
ITERABLE: 2,
POJO: 1,
} as const;

export type TsonStructures = typeof TsonStructures;

export interface TsonAsyncChunk<T = unknown> {
chunk: T;
key?: null | number | string | undefined;
Expand Down Expand Up @@ -45,7 +77,6 @@ export type TsonAsyncType<
TSerializedType extends SerializedType,
> = TsonTypeTesterCustom & TsonAsyncMarshaller<TValue, TSerializedType>;


export interface TsonAsyncOptions {
/**
* A list of guards to apply to every value
Expand All @@ -63,30 +94,6 @@ export interface TsonAsyncOptions {
types: (TsonAsyncType<any, any> | TsonType<any, any>)[];
}

export const ChunkTypes = {
BODY: "BODY",
ERROR: "ERROR",
HEAD: "HEAD",
LEAF: "LEAF",
REF: "REF",
TAIL: "TAIL",
} as const;

export type ChunkTypes = {
[key in keyof typeof ChunkTypes]: (typeof ChunkTypes)[key];
};

export const TsonStatus = {
//MULTI_STATUS: 207,
ERROR: 500,
INCOMPLETE: 203,
OK: 200,
} as const;

export type TsonStatus = {
[key in keyof typeof TsonStatus]: (typeof TsonStatus)[key];
};

export type TsonAsyncTupleHeader = [
Id: `${TsonNonce}${number}`,
ParentId: `${TsonNonce}${"" | number}`,
Expand All @@ -110,7 +117,7 @@ export type TsonAsyncBodyTuple = [
export type TsonAsyncHeadTuple = [
ChunkType: ChunkTypes["HEAD"],
Header: TsonAsyncTupleHeader,
TypeHandlerKey?: string | undefined,
TypeHandlerKey?: TsonStructures[keyof TsonStructures] | string | undefined,
];

export type TsonAsyncReferenceTuple = [
Expand Down Expand Up @@ -142,3 +149,4 @@ export type TsonAsyncTuple =
| TsonAsyncLeafTuple
| TsonAsyncReferenceTuple
| TsonAsyncTailTuple;

10 changes: 5 additions & 5 deletions src/async/handlers/tsonPromise2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,21 +115,21 @@ test("serialize promise that returns a promise", async () => {
expect(leaves).toHaveLength(3);
expect(tails).toHaveLength(6);

expect(heads[0]).toStrictEqual([ChunkTypes.HEAD, [idOf(0), nonce, null]]);
expect(heads[0]).toStrictEqual([ChunkTypes.HEAD, [idOf(0), nonce, null], 1]);
expect(heads[1]).toStrictEqual([
ChunkTypes.HEAD,
[anyId, idOf(0), "promise"],
tsonPromise.key,
]);

expect(heads[2]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, null]]);
expect(heads[3]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, 1]]);
expect(heads[2]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, null], 0]);
expect(heads[3]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, 1], 1]);
expect(heads[4]).toStrictEqual([
ChunkTypes.HEAD,
[anyId, anyId, "anotherPromise"],
tsonPromise.key,
]);
expect(heads[5]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, null]]);
expect(heads[5]).toStrictEqual([ChunkTypes.HEAD, [anyId, anyId, null], 0]);
});

test("promise that rejects", async () => {
Expand Down Expand Up @@ -184,7 +184,7 @@ test("promise that rejects", async () => {
tsonPromise.key,
]);

expect(heads[1]).toEqual([ChunkTypes.HEAD, [anyId, idOf(0), null]]);
expect(heads[1]).toEqual([ChunkTypes.HEAD, [anyId, idOf(0), null], 0]);
expect(leaves[0]).toEqual([ChunkTypes.LEAF, [anyId, anyId, 0], 1]);
expect(leaves[1]).toEqual([
ChunkTypes.LEAF,
Expand Down
42 changes: 21 additions & 21 deletions src/async/serializeAsync2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import { assertType, describe, expect, test } from "vitest";

import { tsonBigint } from "../index.js";
import { expectSequence } from "../internals/testUtils.js";
import { ChunkTypes, TsonAsyncTuple, TsonStatus } from "./asyncTypes2.js";
import {
ChunkTypes,
TsonAsyncTuple,
TsonStatus,
TsonStructures,
} from "./asyncTypes2.js";
import { tsonPromise } from "./handlers/tsonPromise2.js";
import { createTsonSerializeAsync } from "./serializeAsync2.js";

const nonce = "__tson";
const anyId = expect.stringMatching(`^${nonce}[0-9]+$`);
const idOf = (id: number | string) => `${nonce}${id}`;

describe("serialize", (it) => {
describe("AsyncSerialize", (it) => {
it("should handle primitives correctly", async ({ expect }) => {
const options = {
guards: [],
Expand Down Expand Up @@ -56,7 +61,7 @@ describe("serialize", (it) => {

expect(chunks.length).toBe(3);
expect(chunks).toEqual([
[ChunkTypes.HEAD, [rootId, nonce, null]],
[ChunkTypes.HEAD, [rootId, nonce, null], TsonStructures.POJO],
[ChunkTypes.REF, [anyId, rootId, "self"], rootId],
[ChunkTypes.TAIL, [anyId, rootId, null], TsonStatus.OK],
]);
Expand All @@ -67,22 +72,17 @@ describe("serialize", (it) => {
["string", "hello"],
["boolean", true],
["null", null],
])(
`should serialize %s primitives without a handler`,
async (type, value) => {
const options = { guards: [], nonce: () => nonce, types: [] };
const serialize = createTsonSerializeAsync(options);
const chunks: TsonAsyncTuple[] = [];
for await (const chunk of serialize(value)) {
chunks.push(chunk);
}
])(`should serialize %s primitives without a handler`, async (_, value) => {
const options = { guards: [], nonce: () => nonce, types: [] };
const serialize = createTsonSerializeAsync(options);
const chunks: TsonAsyncTuple[] = [];
for await (const chunk of serialize(value)) {
chunks.push(chunk);
}

expect(chunks.length).toBe(1);
expect(chunks).toEqual([
[ChunkTypes.LEAF, [idOf(0), nonce, null], value],
]);
},
);
expect(chunks.length).toBe(1);
expect(chunks).toEqual([[ChunkTypes.LEAF, [idOf(0), nonce, null], value]]);
});

it("should serialize values with a sync handler", async ({ expect }) => {
const options = {
Expand All @@ -105,9 +105,7 @@ describe("serialize", (it) => {
[ChunkTypes.LEAF, [idOf(0), nonce, null], "0", "bigint"],
]);
});
});

describe("serializeAsync", (it) => {
it("should serialize values with an async handler", async ({ expect }) => {
const options = {
guards: [],
Expand Down Expand Up @@ -153,7 +151,7 @@ describe("serializeAsync", (it) => {

expect(heads).toStrictEqual([
[ChunkTypes.HEAD, [head_1_id, nonce, null], tsonPromise.key],
[ChunkTypes.HEAD, [head_2_id, head_1_id, null]],
[ChunkTypes.HEAD, [head_2_id, head_1_id, null], 0],
]);

expect(leaves).toStrictEqual([
Expand All @@ -166,7 +164,9 @@ describe("serializeAsync", (it) => {
[ChunkTypes.TAIL, [anyId, head_2_id, null], TsonStatus.OK],
]);
});
});

describe("TsonGuards", (it) => {
it("should apply guards and throw if they fail", async ({ expect }) => {
const options = {
guards: [{ assert: (val: unknown) => val !== "fail", key: "testGuard" }],
Expand Down
44 changes: 24 additions & 20 deletions src/async/serializeAsync2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
TsonAsyncTuple,
TsonAsyncType,
TsonStatus,
TsonStructures,
} from "./asyncTypes2.js";
import { isAsyncIterableEsque, isIterableEsque } from "./iterableUtils.js";

Expand Down Expand Up @@ -149,7 +150,6 @@ export function createTsonSerializeAsync(opts: TsonAsyncOptions) {
// Try to find a matching handler and initiate serialization
const handler = selectHandler({ handlers, value });

// fallback to parsing as json
if (!handler) {
applyGuards(value);

Expand All @@ -161,6 +161,7 @@ export function createTsonSerializeAsync(opts: TsonAsyncOptions) {
Promise.resolve([
ChunkTypes.HEAD,
[thisId, parentId, key],
typeofStruct(value),
] as TsonAsyncHeadTuple).then(initializeIterable(iterator)),
);

Expand Down Expand Up @@ -309,25 +310,28 @@ async function* toAsyncGenerator<T extends object>(
}
}

// function typeofStruct<
// T extends
// | AsyncIterable<any>
// | Iterable<any>
// | Record<number | string, any>
// | any[],
// >(item: T): "array" | "iterable" | "pojo" {
// switch (true) {
// case Symbol.asyncIterator in item:
// return "iterable";
// case Array.isArray(item):
// return "array";
// case Symbol.iterator in item:
// return "iterable";
// default:
// // we intentionally treat functions as pojos
// return "pojo";
// }
// }
function typeofStruct<
T extends
| AsyncIterable<any>
| Iterable<any>
| Record<number | string, any>
| any[],
>(item: T): TsonStructures[keyof TsonStructures] {
switch (true) {
case Symbol.asyncIterator in item:
return TsonStructures.ITERABLE;

Check warning on line 322 in src/async/serializeAsync2.ts

View check run for this annotation

Codecov / codecov/patch

src/async/serializeAsync2.ts#L322

Added line #L322 was not covered by tests
case Array.isArray(item):
return TsonStructures.ARRAY;
case Symbol.iterator in item:
return TsonStructures.ITERABLE;

Check warning on line 326 in src/async/serializeAsync2.ts

View check run for this annotation

Codecov / codecov/patch

src/async/serializeAsync2.ts#L326

Added line #L326 was not covered by tests
case typeof item === "object":
case typeof item === "function":
// we intentionally treat functions as pojos
return TsonStructures.POJO;
default:
throw new Error("Unexpected type");

Check warning on line 332 in src/async/serializeAsync2.ts

View check run for this annotation

Codecov / codecov/patch

src/async/serializeAsync2.ts#L332

Added line #L332 was not covered by tests
}
}

// /**
// * - Async iterables are iterated, and each value yielded is walked.
Expand Down

0 comments on commit 9aa3dc1

Please sign in to comment.