Skip to content

Commit

Permalink
refactor(experimental): allow variable-size number codecs in getBoole…
Browse files Browse the repository at this point in the history
…anCodec
  • Loading branch information
lorisleiva committed Apr 4, 2024
1 parent 164e455 commit e345282
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-toes-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@solana/codecs-data-structures': patch
---

The `getBooleanCodec` function now accepts variable-size number codecs
96 changes: 68 additions & 28 deletions packages/codecs-data-structures/src/__tests__/boolean-test.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,76 @@
import { mapCodec } from '@solana/codecs-core';
import { getShortU16Codec, getU32Codec } from '@solana/codecs-numbers';
import { SOLANA_ERROR__CODECS__EXPECTED_FIXED_LENGTH, SolanaError } from '@solana/errors';

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

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

it('encodes booleans', () => {
// Encode.
expect(boolean().encode(true)).toStrictEqual(b('01'));
expect(boolean().encode(false)).toStrictEqual(b('00'));
expect(boolean({ size: u32() }).encode(true)).toStrictEqual(b('01000000'));
expect(boolean({ size: u32() }).encode(false)).toStrictEqual(b('00000000'));

// Decode.
expect(boolean().read(b('01'), 0)).toStrictEqual([true, 1]);
expect(boolean().read(b('00'), 0)).toStrictEqual([false, 1]);
expect(boolean().read(b('ffff01'), 2)).toStrictEqual([true, 3]);
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.
expect(() => boolean({ size: getShortU16Codec() })).toThrow(
new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_FIXED_LENGTH),
);
});

it('has the right sizes', () => {
expect(boolean().fixedSize).toBe(1);
expect(boolean({ size: u32() }).fixedSize).toBe(4);
// A variable-size number codecs that uses 0 for `false`
// and the max shortU16 value for `true`.
const mappedShortU16 = mapCodec(
getShortU16Codec(),
// eslint-disable-next-line jest/no-conditional-in-test
v => (v === 0 ? 0 : 0xffff),
// eslint-disable-next-line jest/no-conditional-in-test
v => (v === 0 ? 0 : 1),
);

it('encodes booleans using a u8 number', () => {
expect(getBooleanCodec().encode(true)).toStrictEqual(b('01'));
expect(getBooleanCodec().encode(false)).toStrictEqual(b('00'));
});

it('decodes booleans using a u8 number', () => {
expect(getBooleanCodec().decode(b('01'))).toBe(true);
expect(getBooleanCodec().decode(b('00'))).toBe(false);
});

it('encodes booleans using a custom fixed-size number codec', () => {
const codec = getBooleanCodec({ size: getU32Codec() });
expect(codec.encode(true)).toStrictEqual(b('01000000'));
expect(codec.encode(false)).toStrictEqual(b('00000000'));
});

it('decodes booleans using a custom fixed-size number codec', () => {
const codec = getBooleanCodec({ size: getU32Codec() });
expect(codec.decode(b('01000000'))).toBe(true);
expect(codec.decode(b('00000000'))).toBe(false);
});

it('encodes booleans using a custom variable-size number codec', () => {
const codec = getBooleanCodec({ size: mappedShortU16 });
expect(codec.encode(true)).toStrictEqual(b('ffff03'));
expect(codec.encode(false)).toStrictEqual(b('00'));
});

it('decodes booleans using a custom variable-size number codec', () => {
const codec = getBooleanCodec({ size: mappedShortU16 });
expect(codec.decode(b('ffff03'))).toBe(true);
expect(codec.decode(b('00'))).toBe(false);
});

it('pushes the offset forward when writing', () => {
expect(getBooleanCodec().write(true, new Uint8Array(10), 6)).toBe(7);
});

it('pushes the offset forward when reading', () => {
expect(getBooleanCodec().read(b('ffff00'), 2)).toStrictEqual([false, 3]);
});

it('returns the correct default fixed size', () => {
const codec = getBooleanCodec();
expect(codec.fixedSize).toBe(1);
});

it('returns the correct custom fixed size', () => {
const codec = getBooleanCodec({ size: getU32Codec() });
expect(codec.fixedSize).toBe(4);
});

it('returns the correct custom variable size', () => {
const codec = getBooleanCodec({ size: mappedShortU16 });
expect(codec.getSizeFromValue(false)).toBe(1);
expect(codec.getSizeFromValue(true)).toBe(3);
expect(codec.maxSize).toBe(3);
});
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import { Codec, Decoder, Encoder, FixedSizeCodec, FixedSizeDecoder, FixedSizeEncoder } from '@solana/codecs-core';
import { getU32Codec, getU32Decoder, getU32Encoder } from '@solana/codecs-numbers';
import {
FixedSizeCodec,
FixedSizeDecoder,
FixedSizeEncoder,
VariableSizeCodec,
VariableSizeDecoder,
VariableSizeEncoder,
} from '@solana/codecs-core';
import {
getShortU16Codec,
getShortU16Decoder,
getShortU16Encoder,
getU32Codec,
getU32Decoder,
getU32Encoder,
} from '@solana/codecs-numbers';

import { getBooleanCodec, getBooleanDecoder, getBooleanEncoder } from '../boolean';

{
// [getBooleanEncoder]: It knows if the encoder is fixed size or variable size.
getBooleanEncoder() satisfies FixedSizeEncoder<boolean, 1>;
getBooleanEncoder({ size: getU32Encoder() }) satisfies FixedSizeEncoder<boolean, 4>;
getBooleanEncoder({ size: {} as Encoder<number> }) satisfies Encoder<boolean>;
getBooleanEncoder({ size: getShortU16Encoder() }) satisfies VariableSizeEncoder<boolean>;
}

{
// [getBooleanDecoder]: It knows if the decoder is fixed size or variable size.
getBooleanDecoder() satisfies FixedSizeDecoder<boolean, 1>;
getBooleanDecoder({ size: getU32Decoder() }) satisfies FixedSizeDecoder<boolean, 4>;
getBooleanDecoder({ size: {} as Decoder<number> }) satisfies Decoder<boolean>;
getBooleanDecoder({ size: getShortU16Decoder() }) satisfies VariableSizeDecoder<boolean>;
}

{
// [getBooleanCodec]: It knows if the codec is fixed size or variable size.
getBooleanCodec() satisfies FixedSizeCodec<boolean, boolean, 1>;
getBooleanCodec({ size: getU32Codec() }) satisfies FixedSizeCodec<boolean, boolean, 4>;
getBooleanCodec({ size: {} as Codec<number> }) satisfies Codec<boolean>;
getBooleanCodec({ size: getShortU16Codec() }) satisfies VariableSizeCodec<boolean>;
}
18 changes: 8 additions & 10 deletions packages/codecs-data-structures/src/boolean.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
assertIsFixedSize,
Codec,
combineCodec,
Decoder,
Expand All @@ -9,6 +8,9 @@ import {
FixedSizeEncoder,
mapDecoder,
mapEncoder,
VariableSizeCodec,
VariableSizeDecoder,
VariableSizeEncoder,
} from '@solana/codecs-core';
import {
FixedSizeNumberCodec,
Expand Down Expand Up @@ -39,11 +41,9 @@ export function getBooleanEncoder(): FixedSizeEncoder<boolean, 1>;
export function getBooleanEncoder<TSize extends number>(
config: BooleanCodecConfig<NumberEncoder> & { size: FixedSizeNumberEncoder<TSize> },
): FixedSizeEncoder<boolean, TSize>;
export function getBooleanEncoder(config: BooleanCodecConfig<NumberEncoder>): Encoder<boolean>;
export function getBooleanEncoder(config: BooleanCodecConfig<NumberEncoder>): VariableSizeEncoder<boolean>;
export function getBooleanEncoder(config: BooleanCodecConfig<NumberEncoder> = {}): Encoder<boolean> {
const size = config.size ?? getU8Encoder();
assertIsFixedSize(size);
return mapEncoder(size, (value: boolean) => (value ? 1 : 0));
return mapEncoder(config.size ?? getU8Encoder(), (value: boolean) => (value ? 1 : 0));
}

/**
Expand All @@ -55,11 +55,9 @@ export function getBooleanDecoder(): FixedSizeDecoder<boolean, 1>;
export function getBooleanDecoder<TSize extends number>(
config: BooleanCodecConfig<NumberDecoder> & { size: FixedSizeNumberDecoder<TSize> },
): FixedSizeDecoder<boolean, TSize>;
export function getBooleanDecoder(config: BooleanCodecConfig<NumberDecoder>): Decoder<boolean>;
export function getBooleanDecoder(config: BooleanCodecConfig<NumberDecoder>): VariableSizeDecoder<boolean>;
export function getBooleanDecoder(config: BooleanCodecConfig<NumberDecoder> = {}): Decoder<boolean> {
const size = config.size ?? getU8Decoder();
assertIsFixedSize(size);
return mapDecoder(size, (value: bigint | number): boolean => Number(value) === 1);
return mapDecoder(config.size ?? getU8Decoder(), (value: bigint | number): boolean => Number(value) === 1);
}

/**
Expand All @@ -71,7 +69,7 @@ export function getBooleanCodec(): FixedSizeCodec<boolean, boolean, 1>;
export function getBooleanCodec<TSize extends number>(
config: BooleanCodecConfig<NumberCodec> & { size: FixedSizeNumberCodec<TSize> },
): FixedSizeCodec<boolean, boolean, TSize>;
export function getBooleanCodec(config: BooleanCodecConfig<NumberCodec>): Codec<boolean>;
export function getBooleanCodec(config: BooleanCodecConfig<NumberCodec>): VariableSizeCodec<boolean>;
export function getBooleanCodec(config: BooleanCodecConfig<NumberCodec> = {}): Codec<boolean> {
return combineCodec(getBooleanEncoder(config), getBooleanDecoder(config));
}

0 comments on commit e345282

Please sign in to comment.