packages/types-codec/src/base/Vec.ts
// Copyright 2017-2024 @polkadot/types-codec authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@polkadot/util/types';
import type { Codec, CodecClass, DefinitionSetter, Registry } from '../types/index.js';
import { compactFromU8aLim, identity, isHex, isU8a, logger, stringify, u8aToU8a } from '@polkadot/util';
import { AbstractArray } from '../abstract/Array.js';
import { decodeU8aVec, typeToConstructor } from '../utils/index.js';
const MAX_LENGTH = 64 * 1024;
const l = logger('Vec');
function decodeVecLength (value: Uint8Array | HexString | unknown[]): [Uint8Array | unknown[] | null, number, number] {
if (Array.isArray(value)) {
return [value, value.length, 0];
} else if (isU8a(value) || isHex(value)) {
const u8a = u8aToU8a(value);
const [startAt, length] = compactFromU8aLim(u8a);
if (length > MAX_LENGTH) {
throw new Error(`Vec length ${length.toString()} exceeds ${MAX_LENGTH}`);
}
return [u8a, length, startAt];
} else if (!value) {
return [null, 0, 0];
}
throw new Error(`Expected array/hex input to Vec<*> decoding, found ${typeof value}: ${stringify(value)}`);
}
export function decodeVec<T extends Codec> (registry: Registry, result: T[], value: Uint8Array | HexString | unknown[] | null, startAt: number, Type: CodecClass<T>): [number, number] {
if (Array.isArray(value)) {
const count = result.length;
for (let i = 0; i < count; i++) {
// 26/08/2022 this is actually a false positive - after recent eslint upgdates
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const entry = value[i];
try {
result[i] = entry instanceof Type
? entry
: new Type(registry, entry);
} catch (error) {
l.error(`Unable to decode on index ${i}`, (error as Error).message);
throw error;
}
}
return [0, 0];
} else if (!value) {
return [0, 0];
}
// we don't need more checks, we already limited it via the length decoding
return decodeU8aVec(registry, result, u8aToU8a(value), startAt, Type);
}
/**
* @name Vec
* @description
* This manages codec arrays. Internally it keeps track of the length (as decoded) and allows
* construction with the passed `Type` in the constructor. It is an extension to Array, providing
* specific encoding/decoding on top of the base type.
*/
export class Vec<T extends Codec> extends AbstractArray<T> {
#Type: CodecClass<T>;
constructor (registry: Registry, Type: CodecClass<T> | string, value: Uint8Array | HexString | unknown[] = [], { definition, setDefinition = identity }: DefinitionSetter<CodecClass<T>> = {}) {
const [decodeFrom, length, startAt] = decodeVecLength(value);
super(registry, length);
this.#Type = definition || setDefinition(typeToConstructor<T>(registry, Type));
this.initialU8aLength = (
isU8a(decodeFrom)
? decodeU8aVec(registry, this, decodeFrom, startAt, this.#Type)
: decodeVec(registry, this, decodeFrom, startAt, this.#Type)
)[0];
}
public static with<O extends Codec> (Type: CodecClass<O> | string): CodecClass<Vec<O>> {
let definition: CodecClass<O> | undefined;
// eslint-disable-next-line no-return-assign
const setDefinition = <T> (d: CodecClass<T>) =>
(definition = d as unknown as CodecClass<O>) as unknown as CodecClass<T>;
return class extends Vec<O> {
constructor (registry: Registry, value?: any[]) {
super(registry, Type, value, { definition, setDefinition });
}
};
}
/**
* @description The type for the items
*/
public get Type (): string {
return this.#Type.name;
}
/**
* @description Finds the index of the value in the array
*/
public override indexOf (other?: unknown): number {
// convert type first, this removes overhead from the eq
const check = other instanceof this.#Type
? other
: new this.#Type(this.registry, other);
for (let i = 0, count = this.length; i < count; i++) {
if (check.eq(this[i])) {
return i;
}
}
return -1;
}
/**
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `Vec<${this.registry.getClassName(this.#Type) || new this.#Type(this.registry).toRawType()}>`;
}
}