This repository has been archived by the owner on Jan 13, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 919
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(experimental): add resizeCodec helper to @solana/codecs-core
- Loading branch information
1 parent
71d404f
commit a98fa64
Showing
9 changed files
with
301 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, SolanaError } from '@solana/errors'; | ||
|
||
import { FixedSizeCodec } from '../codec'; | ||
import { resizeCodec } from '../resize-codec'; | ||
import { getMockCodec } from './__setup__'; | ||
|
||
describe('resizeCodec', () => { | ||
it('resizes fixed-size codecs', () => { | ||
const mockCodec = getMockCodec({ size: 42 }) as FixedSizeCodec<unknown, 42>; | ||
expect(resizeCodec(mockCodec, size => size + 1).fixedSize).toBe(43); | ||
expect(resizeCodec(mockCodec, size => size * 2).fixedSize).toBe(84); | ||
expect(resizeCodec(mockCodec, () => 0).fixedSize).toBe(0); | ||
}); | ||
|
||
it('resizes variable-size codecs', () => { | ||
const mockCodec = getMockCodec(); | ||
mockCodec.getSizeFromValue.mockReturnValue(42); | ||
expect(resizeCodec(mockCodec, size => size + 1).getSizeFromValue(null)).toBe(43); | ||
expect(resizeCodec(mockCodec, size => size * 2).getSizeFromValue(null)).toBe(84); | ||
expect(resizeCodec(mockCodec, () => 0).getSizeFromValue(null)).toBe(0); | ||
}); | ||
|
||
it('throws when fixed-size codecs have negative sizes', () => { | ||
const mockCodec = getMockCodec({ size: 42 }) as FixedSizeCodec<unknown, 42>; | ||
expect(() => resizeCodec(mockCodec, size => size - 100).fixedSize).toThrow( | ||
new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, { | ||
bytesLength: -58, | ||
codecDescription: 'resizeEncoder', | ||
}), | ||
); | ||
}); | ||
|
||
it('throws when variable-size codecs have negative sizes', () => { | ||
const mockCodec = getMockCodec(); | ||
mockCodec.getSizeFromValue.mockReturnValue(42); | ||
expect(() => resizeCodec(mockCodec, size => size - 100).getSizeFromValue(null)).toThrow( | ||
new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, { | ||
bytesLength: -58, | ||
codecDescription: 'resizeEncoder', | ||
}), | ||
); | ||
}); | ||
}); |
77 changes: 77 additions & 0 deletions
77
packages/codecs-core/src/__typetests__/resize-codec-typetest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { | ||
Codec, | ||
Decoder, | ||
Encoder, | ||
FixedSizeCodec, | ||
FixedSizeDecoder, | ||
FixedSizeEncoder, | ||
VariableSizeCodec, | ||
VariableSizeDecoder, | ||
VariableSizeEncoder, | ||
} from '../codec'; | ||
import { resizeCodec, resizeDecoder, resizeEncoder } from '../resize-codec'; | ||
|
||
type NumberToArray<N extends number, T extends unknown[] = []> = T['length'] extends N | ||
? T | ||
: NumberToArray<N, [...T, unknown]>; | ||
type Increment<N extends number> = [...NumberToArray<N>, unknown]['length']; | ||
|
||
{ | ||
// [resizeEncoder]: It returns the same encoder type as the one provided for non-fixed size encoders. | ||
type BrandedEncoder = Encoder<42> & { readonly __brand: unique symbol }; | ||
const resize = (size: number) => size * 2; | ||
resizeEncoder({} as BrandedEncoder, resize) satisfies BrandedEncoder; | ||
resizeEncoder({} as VariableSizeEncoder<string>, resize) satisfies VariableSizeEncoder<string>; | ||
resizeEncoder({} as Encoder<string>, resize) satisfies Encoder<string>; | ||
} | ||
|
||
{ | ||
// [resizeEncoder]: It uses the resize ReturnType as size for fixed-size encoders. | ||
const doubleResize = (size: number): number => size * 2; | ||
const encoder = {} as FixedSizeEncoder<string, 42>; | ||
resizeEncoder(encoder, doubleResize) satisfies FixedSizeEncoder<string, number>; | ||
// @ts-expect-error We no longer know if the fixed size is 42. | ||
resizeEncoder(encoder, doubleResize) satisfies FixedSizeEncoder<string, 42>; | ||
const incrementResize = <TSize extends number>(size: TSize) => (size + 1) as Increment<TSize>; | ||
resizeEncoder(encoder, incrementResize) satisfies FixedSizeEncoder<string, 43>; | ||
} | ||
|
||
{ | ||
// [resizeDecoder]: It returns the same decoder type as the one provided for non-fixed size decoders. | ||
type BrandedDecoder = Decoder<42> & { readonly __brand: unique symbol }; | ||
const resize = (size: number) => size * 2; | ||
resizeDecoder({} as BrandedDecoder, resize) satisfies BrandedDecoder; | ||
resizeDecoder({} as VariableSizeDecoder<string>, resize) satisfies VariableSizeDecoder<string>; | ||
resizeDecoder({} as Decoder<string>, resize) satisfies Decoder<string>; | ||
} | ||
|
||
{ | ||
// [resizeDecoder]: It uses the resize ReturnType as size for fixed-size decoders. | ||
const doubleResize = (size: number): number => size * 2; | ||
const decoder = {} as FixedSizeDecoder<string, 42>; | ||
resizeDecoder(decoder, doubleResize) satisfies FixedSizeDecoder<string, number>; | ||
// @ts-expect-error We no longer know if the fixed size is 42. | ||
resizeDecoder(decoder, doubleResize) satisfies FixedSizeDecoder<string, 42>; | ||
const incrementResize = <TSize extends number>(size: TSize) => (size + 1) as Increment<TSize>; | ||
resizeDecoder(decoder, incrementResize) satisfies FixedSizeDecoder<string, 43>; | ||
} | ||
|
||
{ | ||
// [resizeCodec]: It returns the same codec type as the one provided for non-fixed size codecs. | ||
type BrandedCodec = Codec<42> & { readonly __brand: unique symbol }; | ||
const resize = (size: number) => size * 2; | ||
resizeCodec({} as BrandedCodec, resize) satisfies BrandedCodec; | ||
resizeCodec({} as VariableSizeCodec<string>, resize) satisfies VariableSizeCodec<string>; | ||
resizeCodec({} as Codec<string>, resize) satisfies Codec<string>; | ||
} | ||
|
||
{ | ||
// [resizeCodec]: It uses the resize ReturnType as size for fixed-size codecs. | ||
const doubleResize = (size: number): number => size * 2; | ||
const codec = {} as FixedSizeCodec<string, string, 42>; | ||
resizeCodec(codec, doubleResize) satisfies FixedSizeCodec<string, string, number>; | ||
// @ts-expect-error We no longer know if the fixed size is 42. | ||
resizeCodec(codec, doubleResize) satisfies FixedSizeCodec<string, 42>; | ||
const incrementResize = <TSize extends number>(size: TSize) => (size + 1) as Increment<TSize>; | ||
resizeCodec(codec, incrementResize) satisfies FixedSizeCodec<string, string, 43>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, SolanaError } from '@solana/errors'; | ||
|
||
import { | ||
Codec, | ||
createDecoder, | ||
createEncoder, | ||
Decoder, | ||
Encoder, | ||
FixedSizeCodec, | ||
FixedSizeDecoder, | ||
FixedSizeEncoder, | ||
isFixedSize, | ||
} from './codec'; | ||
import { combineCodec } from './combine-codec'; | ||
|
||
/** | ||
* Updates the size of a given encoder. | ||
*/ | ||
export function resizeEncoder<TFrom, TSize extends number, TNewSize extends number>( | ||
encoder: FixedSizeEncoder<TFrom, TSize>, | ||
resize: (size: TSize) => TNewSize, | ||
): FixedSizeEncoder<TFrom, TNewSize>; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeEncoder<TEncoder extends Encoder<any>>( | ||
encoder: TEncoder, | ||
resize: (size: number) => number, | ||
): TEncoder; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeEncoder<TEncoder extends Encoder<any>>( | ||
encoder: TEncoder, | ||
resize: (size: number) => number, | ||
): TEncoder { | ||
if (isFixedSize(encoder)) { | ||
const fixedSize = resize(encoder.fixedSize); | ||
if (fixedSize < 0) { | ||
throw new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, { | ||
bytesLength: fixedSize, | ||
codecDescription: 'resizeEncoder', | ||
}); | ||
} | ||
return createEncoder({ ...encoder, fixedSize }) as TEncoder; | ||
} | ||
return createEncoder({ | ||
...encoder, | ||
getSizeFromValue: value => { | ||
const newSize = resize(encoder.getSizeFromValue(value)); | ||
if (newSize < 0) { | ||
throw new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, { | ||
bytesLength: newSize, | ||
codecDescription: 'resizeEncoder', | ||
}); | ||
} | ||
return newSize; | ||
}, | ||
}) as TEncoder; | ||
} | ||
|
||
/** | ||
* Updates the size of a given decoder. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
|
||
export function resizeDecoder<TFrom, TSize extends number, TNewSize extends number>( | ||
decoder: FixedSizeDecoder<TFrom, TSize>, | ||
resize: (size: TSize) => TNewSize, | ||
): FixedSizeDecoder<TFrom, TNewSize>; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeDecoder<TDecoder extends Decoder<any>>( | ||
decoder: TDecoder, | ||
resize: (size: number) => number, | ||
): TDecoder; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeDecoder<TDecoder extends Decoder<any>>( | ||
decoder: TDecoder, | ||
resize: (size: number) => number, | ||
): TDecoder { | ||
if (isFixedSize(decoder)) { | ||
const fixedSize = resize(decoder.fixedSize); | ||
if (fixedSize < 0) { | ||
throw new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_POSITIVE_BYTE_LENGTH, { | ||
bytesLength: fixedSize, | ||
codecDescription: 'resizeDecoder', | ||
}); | ||
} | ||
return createDecoder({ ...decoder, fixedSize }) as TDecoder; | ||
} | ||
return decoder; | ||
} | ||
|
||
/** | ||
* Updates the size of a given codec. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeCodec<TFrom, TTo extends TFrom, TSize extends number, TNewSize extends number>( | ||
codec: FixedSizeCodec<TFrom, TTo, TSize>, | ||
resize: (size: TSize) => TNewSize, | ||
): FixedSizeCodec<TFrom, TTo, TNewSize>; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeCodec<TCodec extends Codec<any>>(codec: TCodec, resize: (size: number) => number): TCodec; | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function resizeCodec<TCodec extends Codec<any>>(codec: TCodec, resize: (size: number) => number): TCodec { | ||
return combineCodec(resizeEncoder(codec, resize), resizeDecoder(codec, resize)) as TCodec; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters