Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: misc fixes #140

Merged
merged 2 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 32 additions & 8 deletions codecs/test/__snapshots__/object.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ snapshot[`\$person invalid { name: "", nickName: "", superPower: "", unluckyNumb
snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) { _tag: "a", bar: 123 } 1`] = `
Expand All @@ -82,7 +85,10 @@ snapshot[`\$.object(
snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) { _tag: "b", x: 0, bar: 123 } 1`] = `
Expand All @@ -94,47 +100,65 @@ snapshot[`\$.object(
snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid null 1`] = `ScaleAssertError: value == null`;

snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid { _tag: null, bar: 1 } 1`] = `ScaleAssertError: typeof value._tag !== "string"`;

snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid { _tag: "", bar: 1 } 1`] = `ScaleAssertError: value._tag: invalid tag`;

snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid { _tag: "b", bar: 1 } 1`] = `ScaleAssertError: !("x" in value)`;

snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid { _tag: "b", x: -1, bar: 1 } 1`] = `ScaleAssertError: value.x < 0`;

snapshot[`\$.object(
\$.taggedUnion(
"_tag",
[ { tag: "a", codec: \$.object(Array) }, { tag: "b", codec: \$.object(Array) } ]
[
Variant { tag: "a", codec: \$.object(Array) },
Variant { tag: "b", codec: \$.object(Array) }
]
),
\$.field("bar", \$.u8)
) invalid { _tag: "a", bar: -1 } 1`] = `ScaleAssertError: value.bar < 0`;
7 changes: 3 additions & 4 deletions codecs/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { Codec, createCodec, Expand, metadata, Narrow, ScaleAssertError, ScaleDe
import { constant } from "./constant.ts"
import { field, NativeObject, object, ObjectMembers } from "./object.ts"

export interface Variant<T extends string, V> {
tag: T
codec: Codec<V>
export class Variant<T extends string, V> {
constructor(readonly tag: T, readonly codec: Codec<V>) {}
}

export function variant<T extends string, E extends AnyCodec[]>(
tag: T,
...members: ObjectMembers<E>
): Variant<T, NativeObject<E>> {
return { tag, codec: object(...members) }
return new Variant(tag, object(...members))
}

export type NativeTaggedUnion<
Expand Down
82 changes: 48 additions & 34 deletions common/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { DecodeBuffer, EncodeBuffer } from "./buffer.ts"
import { Metadata } from "./metadata.ts"
import { ScaleAssertError, ScaleEncodeError } from "./util.ts"

export type AnyCodec = Codec<any> | Codec<never>
export type Native<T extends AnyCodec> = T extends Codec<infer U> ? U : never

export function createCodec<T>(
Expand Down Expand Up @@ -39,39 +38,8 @@ const codecInspectCtx = new Map<Codec<any>, number | null>()
let codecInspectIdN = 0
const nodeCustomInspect = Symbol.for("nodejs.util.inspect.custom")
const denoCustomInspect = Symbol.for("Deno.customInspect")
export abstract class Codec<T> {
/** A static estimation of the size, which may be an under- or over-estimate */
abstract _staticSize: number
/** Encodes the value into the supplied buffer, which should have at least `_staticSize` free byte. */
abstract _encode: (buffer: EncodeBuffer, value: T) => void
/** Decodes the value from the supplied buffer */
abstract _decode: (buffer: DecodeBuffer) => T
/** Asserts that the value is valid for this codec */
abstract _assert: (state: AssertState) => void
/** An array with metadata representing the construction of this codec */
abstract _metadata: Metadata<T>

/** Encodes the value into a new Uint8Array (throws if async) */
encode(value: T) {
const buf = new EncodeBuffer(this._staticSize)
this._encode(buf, value)
if (buf.asyncCount) throw new ScaleEncodeError(this, value, "Attempted to synchronously encode an async codec")
return buf.finish()
}

/** Asynchronously encodes the value into a new Uint8Array */
async encodeAsync(value: T) {
const buf = new EncodeBuffer(this._staticSize)
this._encode(buf, value)
return buf.finishAsync()
}

/** Decodes a value from the supplied Uint8Array */
decode(array: Uint8Array) {
const buf = new DecodeBuffer(array)
return this._decode(buf)
}

abstract class _Codec {
private [nodeCustomInspect](_0: unknown, _1: unknown, inspect: (value: unknown) => string) {
return this._inspect(inspect)
}
Expand All @@ -82,7 +50,7 @@ export abstract class Codec<T> {

// Properly handles circular codecs in the case of $.deferred
private _inspect(inspect: (value: unknown) => string): string
private _inspect(this: Codec<T>, inspect: (value: unknown) => string): string {
private _inspect<T>(this: Codec<T>, inspect: (value: unknown) => string): string {
let id = codecInspectCtx.get(this)
if (id !== undefined) {
if (id === null) {
Expand Down Expand Up @@ -115,6 +83,52 @@ export abstract class Codec<T> {
}
}

export interface AnyCodec extends _Codec {
_staticSize: number
_encode(buffer: EncodeBuffer, value: any): void
_decode: (buffer: DecodeBuffer) => any
_assert: (state: AssertState) => void
_metadata: any

encode(value: any): Uint8Array
encodeAsync(value: any): Promise<Uint8Array>
decode(array: Uint8Array): any
}

export abstract class Codec<in out T> extends _Codec implements AnyCodec {
/** A static estimation of the size, which may be an under- or over-estimate */
abstract _staticSize: number
/** Encodes the value into the supplied buffer, which should have at least `_staticSize` free byte. */
abstract _encode: (buffer: EncodeBuffer, value: T) => void
/** Decodes the value from the supplied buffer */
abstract _decode: (buffer: DecodeBuffer) => T
/** Asserts that the value is valid for this codec */
abstract _assert: (state: AssertState) => void
/** An array with metadata representing the construction of this codec */
abstract _metadata: Metadata<T>

/** Encodes the value into a new Uint8Array (throws if async) */
encode(value: T) {
const buf = new EncodeBuffer(this._staticSize)
this._encode(buf, value)
if (buf.asyncCount) throw new ScaleEncodeError(this, value, "Attempted to synchronously encode an async codec")
return buf.finish()
}

/** Asynchronously encodes the value into a new Uint8Array */
async encodeAsync(value: T) {
const buf = new EncodeBuffer(this._staticSize)
this._encode(buf, value)
return buf.finishAsync()
}

/** Decodes a value from the supplied Uint8Array */
decode(array: Uint8Array) {
const buf = new DecodeBuffer(array)
return this._decode(buf)
}
}

export function assert<T>(codec: Codec<T>, value: unknown): asserts value is T {
codec._assert(new AssertState(value))
}
Expand Down