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

Commit

Permalink
fix: un-break guards and circular reference checks
Browse files Browse the repository at this point in the history
  • Loading branch information
helmturner committed Nov 26, 2023
1 parent 78491c8 commit 92ffe33
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/internals/isComplexValue.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function isComplexValue(arg: unknown): arg is object {
return arg !== null && (typeof arg === "object" || typeof arg === "function");
return (arg !== null && typeof arg === "object") || typeof arg === "function";
}
107 changes: 71 additions & 36 deletions src/sync/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { GetNonce, getDefaultNonce } from "../internals/getNonce.js";
import { isComplexValue } from "../internals/isComplexValue.js";
import { mapOrReturn } from "../internals/mapOrReturn.js";
import {
SerializedType,
TsonAllTypes,
TsonMarshaller,
TsonNonce,
TsonOptions,
TsonSerializeFn,
Expand Down Expand Up @@ -66,65 +66,100 @@ export function createTsonSerialize(opts: TsonOptions): TsonSerializeFn {

const walk: WalkFn = (value: unknown) => {
const type = typeof value;
const isComplex = isComplexValue(value);

const primitiveHandler = primitives.get(type);
if (isComplex) {
if (seen.has(value)) {
const cached = cache.get(value);
if (!cached) {
throw new TsonCircularReferenceError(value);
}

const handler =
primitiveHandler &&
(!primitiveHandler.test || primitiveHandler.test(value))
? primitiveHandler
: Array.from(nonPrimitives).find((handler) => handler.test(value));

if (!handler) {
for (const guard of guards) {
// if ("assert" in guard) {
guard.assert(value);
// }
//todo: if this is implemented does it go before or after assert?
// if ("parse" in guard) {
// value = guard.parse(value);
// }
return cached;
}

return mapOrReturn(value, walk);
seen.add(value);
}

if (!isComplexValue(value)) {
return toTuple(value, handler);
}
const cacheAndReturn = (result: unknown) => {
if (isComplex) {
cache.set(value, result);
}

// if this is a value-by-reference we've seen before, either:
// - We've serialized & cached it before and can return the cached value
// - We're attempting to serialize it, but one of its children is itself (circular reference)
if (cache.has(value)) {
return cache.get(value);
}
return result;
};

if (seen.has(value)) {
throw new TsonCircularReferenceError(value);
const primitiveHandler = primitives.get(type);
if (
primitiveHandler &&
(!primitiveHandler.test || primitiveHandler.test(value))
) {
return cacheAndReturn(toTuple(value, primitiveHandler));
}

seen.add(value);

const tuple = toTuple(value, handler);
for (const handler of nonPrimitives) {
if (handler.test(value)) {
return cacheAndReturn(toTuple(value, handler));
}
}

cache.set(value, tuple);
for (const guard of guards) {
// if ("assert" in guard) {
guard.assert(value);
// }
//todo: if this is implemented does it go before or after assert?
// if ("parse" in guard) {
// value = guard.parse(value);
// }
}

return tuple;
return cacheAndReturn(mapOrReturn(value, walk));
};

return walk;

function toTuple(
v: unknown,
handler: { key: string; serialize: (arg: unknown) => SerializedType },
handler:
| (TsonTypeTesterCustom & TsonMarshaller<any, any>)
| (TsonTypeTesterPrimitive & Partial<TsonMarshaller<any, any>>),
) {
return [
handler.key as TsonTypeHandlerKey,
walk(handler.serialize(v)),
walk(handler.serialize?.(v)),
nonce,
] as TsonTuple;
}

// if (!handler) {
// return mapOrReturn(value, walk);
// }

// if (!isComplexValue(value)) {
// return toTuple(value, handler);
// }

// // if this is a value-by-reference we've seen before, either:
// // - We've serialized & cached it before and can return the cached value
// // - We're attempting to serialize it, but one of its children is itself (circular reference)
// if (cache.has(value)) {
// return cache.get(value);
// }

// if (seen.has(value)) {
// throw new TsonCircularReferenceError(value);
// }

// seen.add(value);

// const tuple = toTuple(value, handler);

// cache.set(value, tuple);

// return tuple;
// };

// return walk;
};

return ((obj): TsonSerialized => {
Expand Down
3 changes: 1 addition & 2 deletions src/sync/syncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ export type TsonType<
TSerializedType extends SerializedType,
> =
| (TsonTypeTesterCustom & TsonMarshaller<TValue, TSerializedType>)
| (TsonTypeTesterPrimitive &
Partial<TsonMarshaller<TValue, TSerializedType>>);
| (TsonTypeTesterPrimitive & TsonMarshaller<TValue, TSerializedType>);

export interface TsonOptions {
/* eslint-disable jsdoc/informative-docs */
Expand Down

0 comments on commit 92ffe33

Please sign in to comment.