Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions codec/binary.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import { Options, Packr } from 'msgpackr';
import { DecodeError, ExtensionCodec, decode, encode } from '@msgpack/msgpack';
import { Codec } from './types';

const packr = new Packr({
useRecords: false,
moreTypes: true,
bundleStrings: true,
useTimestamp32: false,
useBigIntExtension: true,
skipValues: [undefined],
} as Options);
const BIGINT_EXT_TYPE = 0;
const extensionCodec = new ExtensionCodec();
extensionCodec.register({
type: BIGINT_EXT_TYPE,
encode(input: unknown): Uint8Array | null {
if (typeof input === 'bigint') {
if (
input <= Number.MAX_SAFE_INTEGER &&
input >= Number.MIN_SAFE_INTEGER
) {
return encode(Number(input));
} else {
return encode(String(input));
}
} else {
return null;
}
},
decode(data: Uint8Array): bigint {
const val = decode(data);
if (!(typeof val === 'string' || typeof val === 'number')) {
throw new DecodeError(`unexpected BigInt source: ${typeof val}`);
}

return BigInt(val);
},
});

/**
* Binary codec, uses [msgpackr](https://www.npmjs.com/package/msgpackr) under the hood
* Binary codec, uses [msgpack](https://www.npmjs.com/package/@msgpack/msgpack) under the hood
* @type {Codec}
*/
export const BinaryCodec: Codec = {
toBuffer(obj) {
return packr.pack(obj);
return encode(obj, {
ignoreUndefined: true,
initialBufferSize: 512,
extensionCodec,
});
},
fromBuffer: (buff: Uint8Array) => {
const res: unknown = packr.unpack(buff);
const res = decode(buff, { extensionCodec });
if (typeof res !== 'object' || res === null) {
throw new Error('unpacked msg is not an object');
}
Expand Down
75 changes: 34 additions & 41 deletions codec/codec.test.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,47 @@
import { describe, test, expect } from 'vitest';
import { codecs } from '../testUtil/fixtures/codec';

describe.each(codecs)('codec -- $name', ({ codec }) => {
test('empty object', () => {
const msg = {};
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('simple test', () => {
const msg = { abc: 123, def: 'cool' };
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('encodes null properly', () => {
const msg = { test: null };
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('encodes the empty buffer properly', () => {
const msg = { test: new Uint8Array(0) };
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('skips optional fields', () => {
const msg = { test: undefined };
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual({});
});

test('encodes bigint properly', () => {
const msg = { big: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) };
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('deeply nested test', () => {
const msg = {
const examples: Array<{
name: string;
obj: Record<string, unknown>;
expected?: Record<string, unknown>;
}> = [
{ name: 'empty object', obj: {} },
{ name: 'simple object', obj: { abc: 123, def: 'cool' } },
{ name: 'non-utf8 string', obj: { test: '🇧🇪你好世界' } },
{ name: 'null value', obj: { test: null } },
{ name: 'empty buffer', obj: { test: new Uint8Array(0) } },
{ name: 'optional field', obj: { test: undefined }, expected: {} },
{
name: 'bigint value',
obj: { big: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1) },
},
{
name: 'deeply nested',
obj: {
array: [{ object: true }],
deeply: {
nested: {
nice: null,
},
},
};
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
});

test('buffer test', () => {
const msg = {
},
},
{
name: 'buffer test',
obj: {
buff: Uint8Array.from([0, 42, 100, 255]),
};
expect(codec.fromBuffer(codec.toBuffer(msg))).toStrictEqual(msg);
},
},
];

describe.each(codecs)('codec -- $name', ({ codec }) => {
describe.each(examples)('example -- $name', ({ obj, expected }) => {
test('encodes and decodes correctly', () => {
expect(codec.fromBuffer(codec.toBuffer(obj))).toStrictEqual(
expected ?? obj,
);
});
});

test('invalid json throws', () => {
Expand Down
Loading
Loading