@solana/codecs-data-structures
Version:
Codecs for various data structures
188 lines (179 loc) • 6.84 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Codec, Decoder, Encoder, ReadonlyUint8Array } from '@solana/codecs-core';
import { getUnionCodec, getUnionDecoder, getUnionEncoder } from './union';
import {
GetDecoderTypeFromVariants,
GetEncoderTypeFromVariants,
GetUnionCodecType,
GetUnionDecoderType,
GetUnionEncoderType,
} from './utils';
// Keep predicate branches on one value type. Pattern matching supports type guards for narrowed branches.
type SameType<TExpected, TActual> = [TExpected] extends [TActual]
? [TActual] extends [TExpected]
? unknown
: never
: never;
type SameEncoderType<TIfTrue extends Encoder<any>, TIfFalse extends Encoder<any>> = SameType<
GetEncoderTypeFromVariants<readonly [TIfTrue]>,
GetEncoderTypeFromVariants<readonly [TIfFalse]>
>;
type SameDecoderType<TIfTrue extends Decoder<any>, TIfFalse extends Decoder<any>> = SameType<
GetDecoderTypeFromVariants<readonly [TIfTrue]>,
GetDecoderTypeFromVariants<readonly [TIfFalse]>
>;
type SameCodecType<TIfTrue extends Codec<any>, TIfFalse extends Codec<any>> = SameType<
GetEncoderTypeFromVariants<readonly [TIfTrue]>,
GetEncoderTypeFromVariants<readonly [TIfFalse]>
> &
SameType<GetDecoderTypeFromVariants<readonly [TIfTrue]>, GetDecoderTypeFromVariants<readonly [TIfFalse]>>;
/**
* Returns an encoder that selects between two encoders based on a predicate.
*
* This encoder uses a boolean predicate function to determine which of two
* encoders to use for a given value. If the predicate returns `true`, the
* `ifTrue` encoder is used; otherwise, the `ifFalse` encoder is used.
*
* @typeParam TFrom - The type of the value to encode.
*
* @param predicate - A function that returns `true` or `false` for a given value.
* @param ifTrue - The encoder to use when the predicate returns `true`.
* @param ifFalse - The encoder to use when the predicate returns `false`.
* @returns An `Encoder` based on the provided encoders.
*
* @example
* Encoding small and large numbers differently.
* ```ts
* const encoder = getPredicateEncoder(
* (n: number) => n < 256,
* getU8Encoder(),
* getU32Encoder()
* );
*
* encoder.encode(42);
* // 0x2a
* // └── Small number encoded as u8
*
* encoder.encode(1000);
* // 0xe8030000
* // └── Large number encoded as u32
* ```
*
* @see {@link getPredicateCodec}
*/
export function getPredicateEncoder<
TFrom = any,
const TIfTrue extends Encoder<TFrom> = Encoder<TFrom>,
const TIfFalse extends Encoder<TFrom> = Encoder<TFrom>,
>(
predicate: (value: TFrom) => boolean,
ifTrue: TIfTrue,
ifFalse: SameEncoderType<TIfTrue, TIfFalse> & TIfFalse,
): GetUnionEncoderType<readonly [TIfTrue, TIfFalse]> {
return getUnionEncoder([ifTrue, ifFalse], (value: GetEncoderTypeFromVariants<readonly [TIfTrue, TIfFalse]>) =>
predicate(value as TFrom) ? 0 : 1,
) as GetUnionEncoderType<readonly [TIfTrue, TIfFalse]>;
}
/**
* Returns a decoder that selects between two decoders based on a predicate.
*
* This decoder uses a boolean predicate function on the raw bytes to determine
* which of two decoders to use. If the predicate returns `true`, the `ifTrue`
* decoder is used; otherwise, the `ifFalse` decoder is used.
*
* @typeParam TTo - The type of the value to decode.
*
* @param predicate - A function that returns `true` or `false` for a given byte array.
* @param ifTrue - The decoder to use when the predicate returns `true`.
* @param ifFalse - The decoder to use when the predicate returns `false`.
* @returns A `Decoder` based on the provided decoders.
*
* @example
* Decoding small and large numbers based on byte length.
* ```ts
* const decoder = getPredicateDecoder(
* bytes => bytes.length === 1,
* getU8Decoder(),
* getU32Decoder()
* );
*
* decoder.decode(new Uint8Array([0x2a]));
* // 42 (decoded as u8)
*
* decoder.decode(new Uint8Array([0xe8, 0x03, 0x00, 0x00]));
* // 1000 (decoded as u32)
* ```
*
* @see {@link getPredicateCodec}
*/
export function getPredicateDecoder<
TTo = any,
const TIfTrue extends Decoder<TTo> = Decoder<TTo>,
const TIfFalse extends Decoder<TTo> = Decoder<TTo>,
>(
predicate: (value: ReadonlyUint8Array) => boolean,
ifTrue: TIfTrue,
ifFalse: SameDecoderType<TIfTrue, TIfFalse> & TIfFalse,
): GetUnionDecoderType<readonly [TIfTrue, TIfFalse]> {
return getUnionDecoder([ifTrue, ifFalse], (value: ReadonlyUint8Array) =>
predicate(value) ? 0 : 1,
) as GetUnionDecoderType<readonly [TIfTrue, TIfFalse]>;
}
/**
* Returns a codec that selects between two codecs based on predicates.
*
* This codec uses boolean predicate functions to determine which of two codecs
* to use for encoding and decoding. If the encoding predicate returns `true`
* for a value, the `ifTrue` codec is used to encode it; otherwise `ifFalse`.
* Similarly, if the decoding predicate returns `true` for the bytes, the
* `ifTrue` codec is used to decode them.
*
* @typeParam TFrom - The type of the value to encode.
* @typeParam TTo - The type of the value to decode.
*
* @param encodePredicate - A function that returns `true` or `false` for a given value.
* @param decodePredicate - A function that returns `true` or `false` for a given byte array.
* @param ifTrue - The codec to use when the respective predicate returns `true`.
* @param ifFalse - The codec to use when the respective predicate returns `false`.
* @returns A `Codec` based on the provided codecs.
*
* @example
* Encoding and decoding small and large numbers differently.
* ```ts
* const codec = getPredicateCodec(
* (n: number) => n < 256,
* bytes => bytes.length === 1,
* getU8Codec(),
* getU32Codec()
* );
*
* const smallBytes = codec.encode(42);
* // 0x2a (encoded as u8)
*
* const largeBytes = codec.encode(1000);
* // 0xe8030000 (encoded as u32)
*
* codec.decode(smallBytes); // 42
* codec.decode(largeBytes); // 1000
* ```
*
* @see {@link getPredicateEncoder}
* @see {@link getPredicateDecoder}
*/
export function getPredicateCodec<
TFrom = any,
TTo extends TFrom = TFrom,
const TIfTrue extends Codec<TFrom, TTo> = Codec<TFrom, TTo>,
const TIfFalse extends Codec<TFrom, TTo> = Codec<TFrom, TTo>,
>(
encodePredicate: (value: TFrom) => boolean,
decodePredicate: (value: ReadonlyUint8Array) => boolean,
ifTrue: TIfTrue,
ifFalse: SameCodecType<TIfTrue, TIfFalse> & TIfFalse,
): GetUnionCodecType<readonly [TIfTrue, TIfFalse]> {
return getUnionCodec(
[ifTrue, ifFalse],
(value: GetEncoderTypeFromVariants<readonly [TIfTrue, TIfFalse]>) => (encodePredicate(value as TFrom) ? 0 : 1),
(value: ReadonlyUint8Array) => (decodePredicate(value) ? 0 : 1),
) as GetUnionCodecType<readonly [TIfTrue, TIfFalse]>;
}