Skip to content

Commit

Permalink
refactor(experimental): errors: codecs-data-structures package (#2190)
Browse files Browse the repository at this point in the history
Adds custom `SolanaError` throws to the `@solana/codecs-data-structures`
package.
  • Loading branch information
buffalojoec committed Feb 29, 2024
1 parent 7712fc3 commit 7d67615
Show file tree
Hide file tree
Showing 20 changed files with 291 additions and 64 deletions.
3 changes: 2 additions & 1 deletion packages/codecs-data-structures/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
},
"dependencies": {
"@solana/codecs-core": "workspace:*",
"@solana/codecs-numbers": "workspace:*"
"@solana/codecs-numbers": "workspace:*",
"@solana/errors": "workspace:*"
},
"devDependencies": {
"@solana/build-scripts": "workspace:*",
Expand Down
15 changes: 13 additions & 2 deletions packages/codecs-data-structures/src/__tests__/array-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getU8Codec, getU16Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors';

import { getArrayCodec } from '../array';
import { b } from './__setup__';
Expand Down Expand Up @@ -57,9 +58,19 @@ describe('getArrayCodec', () => {
expect(arrayU64.read(b('0200000000000000'), 0)).toStrictEqual([[2n], 8]);

// It fails if the array has a different size.
expect(() => array(string(), { size: 1 }).encode([])).toThrow('Expected [array] to have 1 items, got 0.');
expect(() => array(string(), { size: 1 }).encode([])).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 0,
codecDescription: 'array',
expected: 1,
}),
);
expect(() => array(string(), { size: 2 }).encode(['a', 'b', 'c'])).toThrow(
'Expected [array] to have 2 items, got 3.',
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 3,
codecDescription: 'array',
expected: 2,
}),
);
});

Expand Down
10 changes: 9 additions & 1 deletion packages/codecs-data-structures/src/__tests__/bit-array-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_BYTES, SolanaError } from '@solana/errors';

import { getBitArrayCodec } from '../bit-array';
import { b } from './__setup__';

Expand Down Expand Up @@ -59,7 +61,13 @@ describe('getBitArrayCodec', () => {
expect(bitArray(1).read(b('00'), 0)).toStrictEqual([a('00000000'), 1]);

// It fails if the byte array is too short.
expect(() => bitArray(3).read(b('ff'), 0)).toThrow(); // `SolanaError` added in later commit
expect(() => bitArray(3).read(b('ff'), 0)).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_BYTES, {
bytesLength: 1,
codecDescription: 'bitArray',
expected: 3,
}),
);
});

it('has the right sizes', () => {
Expand Down
11 changes: 11 additions & 0 deletions packages/codecs-data-structures/src/__tests__/boolean-test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { getU32Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_CODEC_REQUIRES_FIXED_SIZE, SolanaError } from '@solana/errors';

import { getBooleanCodec } from '../boolean';
import { b } from './__setup__';

describe('getBooleanCodec', () => {
const boolean = getBooleanCodec;
const u32 = getU32Codec;
const string = getStringCodec;

it('encodes booleans', () => {
// Encode.
Expand All @@ -21,6 +24,14 @@ describe('getBooleanCodec', () => {
expect(boolean().read(b('ffff00'), 2)).toStrictEqual([false, 3]);
expect(boolean({ size: u32() }).read(b('01000000'), 0)).toStrictEqual([true, 4]);
expect(boolean({ size: u32() }).read(b('00000000'), 0)).toStrictEqual([false, 4]);

// Fails if the codec is not fixed size.
// @ts-expect-error Boolean codec should be fixed-size.
expect(() => boolean({ size: string() }).read(b('00000000'), 0)).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_CODEC_REQUIRES_FIXED_SIZE, {
codecDescription: 'bool',
}),
);
});

it('has the right sizes', () => {
Expand Down
17 changes: 15 additions & 2 deletions packages/codecs-data-structures/src/__tests__/bytes-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getU8Codec } from '@solana/codecs-numbers';
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_BYTES, SolanaError } from '@solana/errors';

import { getBytesCodec } from '../bytes';
import { b } from './__setup__';
Expand All @@ -15,7 +16,13 @@ describe('getBytesCodec', () => {
expect(bytesU8.read(b('ff022a03ffff'), 1)).toStrictEqual([new Uint8Array([42, 3]), 4]);

// Not enough bytes.
expect(() => bytesU8.read(b('022a'), 0)).toThrow(); // `SolanaError` added in later commit
expect(() => bytesU8.read(b('022a'), 0)).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_BYTES, {
bytesLength: 1,
codecDescription: 'bytes',
expected: 2,
}),
);
});

it('encodes fixed bytes', () => {
Expand All @@ -31,7 +38,13 @@ describe('getBytesCodec', () => {
expect(bytes5.encode(new Uint8Array([1, 2]))).toStrictEqual(b('0102000000'));
expect(bytes5.read(b('0102000000'), 0)).toStrictEqual([new Uint8Array([1, 2, 0, 0, 0]), 5]);
expect(bytes5.read(b('ff0102000000'), 1)).toStrictEqual([new Uint8Array([1, 2, 0, 0, 0]), 6]);
expect(() => bytes5.read(b('0102'), 0)).toThrow(); // `SolanaError` added in later commit
expect(() => bytes5.read(b('0102'), 0)).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_BYTES, {
bytesLength: 2,
codecDescription: 'fixCodec',
expected: 5,
}),
);

// Too large (truncated).
expect(bytes2.encode(new Uint8Array([1, 2, 3, 4, 5]))).toStrictEqual(b('0102'));
Expand Down
18 changes: 14 additions & 4 deletions packages/codecs-data-structures/src/__tests__/data-enum-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { assertIsFixedSize, assertIsVariableSize, isFixedSize, isVariableSize } from '@solana/codecs-core';
import { getU8Codec, getU16Codec, getU32Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import {
SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE,
SOLANA_ERROR__CODECS_INVALID_DATA_ENUM_VARIANT,
SolanaError,
} from '@solana/errors';

import { getArrayCodec } from '../array';
import { getBooleanCodec } from '../boolean';
Expand Down Expand Up @@ -97,12 +102,17 @@ describe('getDataEnumCodec', () => {

it('handles invalid variants', () => {
expect(() => dataEnum(getWebEvent()).encode({ __kind: 'Missing' } as unknown as WebEvent)).toThrow(
'Invalid data enum variant. ' +
'Expected one of [PageLoad, Click, KeyPress, PageUnload], ' +
'got "Missing".',
new SolanaError(SOLANA_ERROR__CODECS_INVALID_DATA_ENUM_VARIANT, {
value: 'Missing',
variants: ['PageLoad', 'Click', 'KeyPress', 'PageUnload'],
}),
);
expect(() => dataEnum(getWebEvent()).read(new Uint8Array([4]), 0)).toThrow(
'Enum discriminator out of range. Expected a number between 0 and 3, got 4.',
new SolanaError(SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE, {
discriminator: 4,
maxRange: 3,
minRange: 0,
}),
);
});

Expand Down
15 changes: 13 additions & 2 deletions packages/codecs-data-structures/src/__tests__/map-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getU8Codec, getU16Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors';

import { getMapCodec } from '../map';
import { b } from './__setup__';
Expand Down Expand Up @@ -69,9 +70,19 @@ describe('getMapCodec', () => {

// It fails if the map has a different size.
expect(() => map(u8(), u8(), { size: 1 }).encode(new Map())).toThrow(
'Expected [array] to have 1 items, got 0.',
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 0,
codecDescription: 'array',
expected: 1,
}),
);
expect(() => letters.encode(lettersMap.set('c', 3))).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 3,
codecDescription: 'array',
expected: 2,
}),
);
expect(() => letters.encode(lettersMap.set('c', 3))).toThrow('Expected [array] to have 2 items, got 3.');
});

it('encodes remainder maps', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FixedSizeCodec } from '@solana/codecs-core';
import { getU8Codec, getU16Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_FIXED_NULLABLE_WITH_VARIABLE_SIZE_CODEC, SolanaError } from '@solana/errors';

import { getNullableCodec } from '../nullable';
import { getUnitCodec } from '../unit';
Expand Down Expand Up @@ -79,7 +80,9 @@ describe('getNullableCodec', () => {

// Fixed nullables must wrap fixed-size items.
// @ts-expect-error It cannot wrap a variable size item when fixed is true.
expect(() => nullable(string(), { fixed: true })).toThrow(); // `SolanaError` added in later commit
expect(() => nullable(string(), { fixed: true })).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_FIXED_NULLABLE_WITH_VARIABLE_SIZE_CODEC),
);
});

it('has the right sizes', () => {
Expand Down
50 changes: 38 additions & 12 deletions packages/codecs-data-structures/src/__tests__/scalar-enum-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getU32Codec, getU64Codec } from '@solana/codecs-numbers';
import {
SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE,
SOLANA_ERROR__CODECS_INVALID_SCALAR_ENUM_VARIANT,
SolanaError,
} from '@solana/errors';

import { getScalarEnumCodec } from '../scalar-enum';
import { b } from './__setup__';
Expand Down Expand Up @@ -47,12 +52,19 @@ describe('getScalarEnumCodec', () => {
// Invalid examples.
// @ts-expect-error Invalid scalar enum variant.
expect(() => scalarEnum(Feedback).encode('Missing')).toThrow(
'Invalid scalar enum variant. ' +
'Expected one of [BAD, GOOD] or a number between 0 and 1, ' +
'got "Missing".',
new SolanaError(SOLANA_ERROR__CODECS_INVALID_SCALAR_ENUM_VARIANT, {
maxRange: 1,
minRange: 0,
value: 'Missing',
variants: ['BAD', 'GOOD'],
}),
);
expect(() => scalarEnum(Feedback).read(new Uint8Array([2]), 0)).toThrow(
'Enum discriminator out of range. Expected a number between 0 and 1, got 2.',
new SolanaError(SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE, {
discriminator: 2,
maxRange: 1,
minRange: 0,
}),
);
});

Expand Down Expand Up @@ -88,12 +100,19 @@ describe('getScalarEnumCodec', () => {
// Invalid examples.
// @ts-expect-error Invalid scalar enum variant.
expect(() => scalarEnum(Direction).encode('Diagonal')).toThrow(
'Invalid scalar enum variant. ' +
'Expected one of [UP, DOWN, LEFT, RIGHT, Up, Down, Left, Right] ' +
'or a number between 0 and 3, got "Diagonal".',
new SolanaError(SOLANA_ERROR__CODECS_INVALID_SCALAR_ENUM_VARIANT, {
maxRange: 3,
minRange: 0,
value: 'Diagonal',
variants: ['UP', 'DOWN', 'LEFT', 'RIGHT', 'Up', 'Down', 'Left', 'Right'],
}),
);
expect(() => scalarEnum(Direction).read(new Uint8Array([4]), 0)).toThrow(
'Enum discriminator out of range. Expected a number between 0 and 3, got 4.',
new SolanaError(SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE, {
discriminator: 4,
maxRange: 3,
minRange: 0,
}),
);
});

Expand All @@ -115,12 +134,19 @@ describe('getScalarEnumCodec', () => {
// Invalid examples.
// @ts-expect-error Invalid scalar enum variant.
expect(() => scalarEnum(Hybrid).encode('Missing')).toThrow(
'Invalid scalar enum variant. ' +
'Expected one of [NUMERIC, LEXICAL, Lexical] ' +
'or a number between 0 and 1, got "Missing".',
new SolanaError(SOLANA_ERROR__CODECS_INVALID_SCALAR_ENUM_VARIANT, {
maxRange: 1,
minRange: 0,
value: 'Missing',
variants: ['NUMERIC', 'LEXICAL', 'Lexical'],
}),
);
expect(() => scalarEnum(Hybrid).read(new Uint8Array([2]), 0)).toThrow(
'Enum discriminator out of range. Expected a number between 0 and 1, got 2.',
new SolanaError(SOLANA_ERROR__CODECS_ENUM_DISCRIMINATOR_OUT_OF_RANGE, {
discriminator: 2,
maxRange: 1,
minRange: 0,
}),
);
});

Expand Down
15 changes: 13 additions & 2 deletions packages/codecs-data-structures/src/__tests__/set-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getU8Codec, getU16Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors';

import { getSetCodec } from '../set';
import { b } from './__setup__';
Expand Down Expand Up @@ -57,9 +58,19 @@ describe('getSetCodec', () => {
expect(setU64.read(b('0200000000000000'), 0)).toStrictEqual([new Set([2n]), 8]);

// It fails if the set has a different size.
expect(() => set(string(), { size: 1 }).encode(new Set())).toThrow('Expected [array] to have 1 items, got 0.');
expect(() => set(string(), { size: 1 }).encode(new Set())).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 0,
codecDescription: 'array',
expected: 1,
}),
);
expect(() => set(string(), { size: 2 }).encode(new Set(['a', 'b', 'c']))).toThrow(
'Expected [array] to have 2 items, got 3.',
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 3,
codecDescription: 'array',
expected: 2,
}),
);
});

Expand Down
11 changes: 11 additions & 0 deletions packages/codecs-data-structures/src/__tests__/tuple-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getI16Codec, getU8Codec, getU64Codec } from '@solana/codecs-numbers';
import { getStringCodec } from '@solana/codecs-strings';
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors';

import { getTupleCodec } from '../tuple';
import { b } from './__setup__';
Expand Down Expand Up @@ -31,6 +32,16 @@ describe('getTupleCodec', () => {
expect(tupleU8U64.decode(b('010200000000000000'))).toStrictEqual([1, 2n]);
expect(tupleU8U64.encode([1, 2n ** 63n])).toStrictEqual(b('010000000000000080'));
expect(tupleU8U64.decode(b('010000000000000080'))).toStrictEqual([1, 2n ** 63n]);

// Fails if given the wrong number of items.
// @ts-expect-error Tuple should have the right number of items.
expect(() => tuple([u8(), u8()]).encode([42])).toThrow(
new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual: 1,
codecDescription: 'tuple',
expected: 2,
}),
);
});

it('has the right sizes', () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/codecs-data-structures/src/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, SolanaError } from '@solana/errors';

/** Checks the number of items in an array-like structure is expected. */
export function assertValidNumberOfItemsForCodec(
codecDescription: string,
expected: number | bigint,
actual: number | bigint,
) {
if (expected !== actual) {
// TODO: Coded error.
throw new Error(`Expected [${codecDescription}] to have ${expected} items, got ${actual}.`);
throw new SolanaError(SOLANA_ERROR__CODECS_WRONG_NUMBER_OF_ITEMS, {
actual,
codecDescription,
expected,
});
}
}

0 comments on commit 7d67615

Please sign in to comment.