euberdeveloper/byteify

View on GitHub
source/modules/serialize.ts

Summary

Maintainability
D
1 day
Test Coverage
A
100%
import {
    ByteifySerializationCannotBeDecimalError,
    ByteifySerializationWrongTypeError,
    ByteifySerializationInputTooSmallError,
    ByteifySerializationInputTooBigError
} from '@/errors/index.js';
import { Essence, NativeType, ByteifyEndianess, ByteifyOptions } from '@/types/index.js';
import { ESSENCE, HANDLER, MAX, MIN, NEGATIVE_SHOULD_BE_ADJUSTED, SUPPORTED_TYPE } from '@/values/index.js';

/**
 * Adjusts the result of the serialization for negative numbers
 * @notExported
 * @category Helper
 * @param bytes The result of the serialization to be adjusted. It will be modified.
 */
function adjustNegative(bytes: number[]): void {
    for (let i = 0; i < bytes.length; i++) {
        bytes[i] = (bytes[i] + 256) % 256;
    }
}

/**
 * Serializes a number into an array of bytes.
 * @notExported
 * @category Helper
 * @param value The number to serialize.
 * @param nativeType The type that is being to be analyzed. It will be used also to take the limits.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The serialized array.
 */
function serialize(value: number | bigint, nativeType: NativeType, options: ByteifyOptions): number[] {
    const essence: Essence = ESSENCE[nativeType];
    const max = MAX[nativeType];
    const min = MIN[nativeType];

    if (SUPPORTED_TYPE[essence] !== typeof value) {
        throw new ByteifySerializationWrongTypeError(
            `Invalid ${nativeType}: value must be a ${SUPPORTED_TYPE[essence]}`,
            nativeType,
            options.endianess,
            value,
            undefined,
            typeof value,
            SUPPORTED_TYPE[essence]
        );
    }

    if (essence !== Essence.DECIMAL) {
        if ((typeof value === 'number' && value % 1 !== 0) || (typeof value === 'bigint' && value % 1n !== 0n)) {
            throw new ByteifySerializationCannotBeDecimalError(
                `Invalid ${nativeType}: value cannot be decimal`,
                nativeType,
                options.endianess,
                value,
                undefined
            );
        }

        if (value < min) {
            throw new ByteifySerializationInputTooSmallError(
                `Invalid ${nativeType}: value cannot be lower than ${min}`,
                nativeType,
                options.endianess,
                value,
                undefined
            );
        }
        if (value > max) {
            throw new ByteifySerializationInputTooBigError(
                `Invalid ${nativeType}: value cannot be higher than ${max}`,
                nativeType,
                options.endianess,
                value,
                undefined
            );
        }
    }

    const SerializationClass = HANDLER[nativeType];
    const result = Array.from(new Uint8Array(new SerializationClass([value]).buffer));
    if (NEGATIVE_SHOULD_BE_ADJUSTED[nativeType]) {
        adjustNegative(result);
    }
    return options.endianess === ByteifyEndianess.LITTLE_ENDIAN ? result : result.reverse();
}

/**
 * Validates and converts a boolean into a number[... to be serialized.
 * @notExported
 * @category Helper
 * @param value The boolean to serialize.
 * @returns The boolean converted into a number.
 */
function purgeBoolean(value: boolean | number): number {
    if (typeof value !== 'boolean' && typeof value !== 'number') {
        throw new ByteifySerializationWrongTypeError(
            `Invalid bool: value must be a boolean`,
            NativeType.BOOL,
            undefined,
            value,
            undefined,
            typeof value,
            'boolean'
        );
    }

    return +value;
}

/**
 * Serializes a boolean into an array of bytes.
 * @param value The boolean to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The boolean serialized in an array of bytes.
 */
export function serializeBool(
    value: boolean,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(purgeBoolean(value), NativeType.BOOL, options);
}

/**
 * Serializes an uint8 into an array of bytes.
 * @param value The uint8 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The uint8 serialized in an array of bytes.
 */
export function serializeUint8(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.UINT8, options);
}

/**
 * Serializes an uint16 into an array of bytes.
 * @param value The uint16 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The uint16 serialized in an array of bytes.
 */
export function serializeUint16(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.UINT16, options);
}

/**
 * Serializes an uint32 into an array of bytes.
 * @param value The uint32 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The uint32 serialized in an array of bytes.
 */
export function serializeUint32(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.UINT32, options);
}

/**
 * Serializes an uint64 into an array of bytes.
 * @param value The uint64 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The uint64 serialized in an array of bytes.
 */
export function serializeUint64(
    value: bigint,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.UINT64, options);
}

/**
 * Serializes an int8 into an array of bytes.
 * @param value The int8 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The int8 serialized in an array of bytes.
 */
export function serializeInt8(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.INT8, options);
}

/**
 * Serializes an int16 into an array of bytes.
 * @param value The int16 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The int16 serialized in an array of bytes.
 */
export function serializeInt16(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.INT16, options);
}

/**
 * Serializes an int32 into an array of bytes.
 * @param value The int32 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The int32 serialized in an array of bytes.
 */
export function serializeInt32(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.INT32, options);
}

/**
 * Serializes an int64 into an array of bytes.
 * @param value The int64 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The int64 serialized in an array of bytes.
 */
export function serializeInt64(
    value: bigint,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.INT64, options);
}

/**
 * Serializes a float32 into an array of bytes.
 * @param value The float32 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The float32 serialized in an array of bytes.
 */
export function serializeFloat32(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.FLOAT32, options);
}

/**
 * Serializes a float64 into an array of bytes.
 * @param value The float64 to serialize.
 * @param options The [[ByteifyOptions]] to use to deserialize the array.
 * @returns The float64 serialized in an array of bytes.
 */
export function serializeFloat64(
    value: number,
    options: ByteifyOptions = { endianess: ByteifyEndianess.LITTLE_ENDIAN }
): number[] {
    return serialize(value, NativeType.FLOAT64, options);
}