Multiple Codec property assignments helper (#4182)
* Multiple Codec property assignments helper * Pass index * consistency in naming * typesToMap external * Loop on index * any -> unknown * CHANGELOG * Adjust * Fix Enum with Array keys
This commit is contained in:
@@ -114,9 +114,14 @@ describe('Enum', (): void => {
|
||||
});
|
||||
|
||||
it('has correct isXyz/asXyz (Enum.with)', (): void => {
|
||||
const test = new (Enum.with({ First: Text, Second: U32, Third: U32 }))(registry, { Second: 42 }) as any as { isSecond: boolean; asSecond: U32; asThird: never };
|
||||
const test = new (Enum.with({ First: Text, Second: U32, Third: U32 }))(registry, { Second: 42 }) as any as { isFirst: boolean; isSecond: boolean; asSecond: U32; isThird: boolean; asThird: never };
|
||||
const asKeys = Object.keys(test).filter((k) => k.startsWith('as'));
|
||||
const isKeys = Object.keys(test).filter((k) => k.startsWith('is'));
|
||||
|
||||
expect(asKeys).toEqual(['asFirst', 'asSecond', 'asThird']);
|
||||
expect(isKeys).toEqual(['isFirst', 'isSecond', 'isThird']);
|
||||
expect([test.isFirst, test.isSecond, test.isThird]).toEqual([false, true, false]);
|
||||
|
||||
expect(test.isSecond).toEqual(true);
|
||||
expect(test.asSecond.toNumber()).toEqual(42);
|
||||
expect((): never => test.asThird).toThrow(/Cannot convert 'Second' via asThird/);
|
||||
});
|
||||
@@ -250,9 +255,9 @@ describe('Enum', (): void => {
|
||||
});
|
||||
|
||||
it('has correct isXyz getters (Enum.with)', (): void => {
|
||||
const test = new (Enum.with(['First', 'Second', 'Third']))(registry, 'Second') as any as { isSecond: boolean; asSecond: never };
|
||||
const test = new (Enum.with(['First', 'Second', 'Third']))(registry, 'Second') as any as { isFirst: boolean; isSecond: boolean; isThird: boolean };
|
||||
|
||||
expect(test.isSecond).toEqual(true);
|
||||
expect([test.isFirst, test.isSecond, test.isThird]).toEqual([false, true, false]);
|
||||
});
|
||||
|
||||
describe('utils', (): void => {
|
||||
|
||||
@@ -8,8 +8,7 @@ import type { AnyJson, Codec, Constructor, IEnum, Registry } from '../types';
|
||||
import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, stringCamelCase, stringify, stringUpperFirst, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { Null } from '../primitive/Null';
|
||||
import { Struct } from './Struct';
|
||||
import { defineProperty, mapToTypeMap } from './utils';
|
||||
import { defineProperties, mapToTypeMap, typesToMap } from './utils';
|
||||
|
||||
// export interface, this is used in Enum.with, so required as public by TS
|
||||
export interface EnumConstructor<T = Codec> {
|
||||
@@ -86,7 +85,7 @@ function extractDef (registry: Registry, _def: Record<string, string | Construct
|
||||
};
|
||||
}
|
||||
|
||||
function createFromValue (registry: Registry, def: TypesDef, index = 0, value?: any): Decoded {
|
||||
function createFromValue (registry: Registry, def: TypesDef, index = 0, value?: unknown): Decoded {
|
||||
const entry = Object.values(def).find((e) => e.index === index);
|
||||
|
||||
assert(!isUndefined(entry), () => `Unable to create Enum via index ${index}, in ${Object.keys(def).join(', ')}`);
|
||||
@@ -99,7 +98,7 @@ function createFromValue (registry: Registry, def: TypesDef, index = 0, value?:
|
||||
};
|
||||
}
|
||||
|
||||
function decodeFromJSON (registry: Registry, def: TypesDef, key: string, value?: any): Decoded {
|
||||
function decodeFromJSON (registry: Registry, def: TypesDef, key: string, value?: unknown): Decoded {
|
||||
// JSON comes in the form of { "<type (camelCase)>": "<value for type>" }, here we
|
||||
// additionally force to lower to ensure forward compat
|
||||
const keys = Object.keys(def).map((k) => k.toLowerCase());
|
||||
@@ -122,7 +121,7 @@ function decodeFromString (registry: Registry, def: TypesDef, value: string): De
|
||||
: decodeFromJSON(registry, def, value);
|
||||
}
|
||||
|
||||
function decodeFromValue (registry: Registry, def: TypesDef, value?: any): Decoded {
|
||||
function decodeFromValue (registry: Registry, def: TypesDef, value?: unknown): Decoded {
|
||||
if (isU8a(value)) {
|
||||
// nested, we don't want to match isObject below
|
||||
if (value.length) {
|
||||
@@ -142,7 +141,7 @@ function decodeFromValue (registry: Registry, def: TypesDef, value?: any): Decod
|
||||
return createFromValue(registry, def, Object.values(def)[0].index);
|
||||
}
|
||||
|
||||
function decodeEnum (registry: Registry, def: TypesDef, value?: any, index?: number): Decoded {
|
||||
function decodeEnum (registry: Registry, def: TypesDef, value?: unknown, index?: number): Decoded {
|
||||
// NOTE We check the index path first, before looking at values - this allows treating
|
||||
// the optional indexes before anything else, more-specific > less-specific
|
||||
if (isNumber(index)) {
|
||||
@@ -198,25 +197,29 @@ export class Enum implements IEnum {
|
||||
}
|
||||
|
||||
public static with (Types: Record<string, string | Constructor> | Record<string, number> | string[]): EnumConstructor<Enum> {
|
||||
const keys = Array.isArray(Types)
|
||||
? Types
|
||||
: Object.keys(Types);
|
||||
const asKeys = new Array<string>(keys.length);
|
||||
const isKeys = new Array<string>(keys.length);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const name = stringUpperFirst(stringCamelCase(keys[i].replace(' ', '_')));
|
||||
|
||||
asKeys[i] = `as${name}`;
|
||||
isKeys[i] = `is${name}`;
|
||||
}
|
||||
|
||||
return class extends Enum {
|
||||
constructor (registry: Registry, value?: unknown, index?: number) {
|
||||
super(registry, Types, value, index);
|
||||
|
||||
const keys = Object.keys(this.#def);
|
||||
defineProperties(this, isKeys, (_, i) => this.type === keys[i]);
|
||||
defineProperties(this, asKeys, (k, i): Codec => {
|
||||
assert(this[isKeys[i] as keyof this], () => `Cannot convert '${this.type}' via ${k}`);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const name = stringUpperFirst(stringCamelCase(key.replace(' ', '_')));
|
||||
const askey = `as${name}`;
|
||||
const iskey = `is${name}`;
|
||||
|
||||
defineProperty(this, iskey, () => this.type === key);
|
||||
defineProperty(this, askey, (): Codec => {
|
||||
assert(this[iskey as keyof this], () => `Cannot convert '${this.type}' via ${askey}`);
|
||||
|
||||
return this.value;
|
||||
});
|
||||
}
|
||||
return this.value;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -376,7 +379,7 @@ export class Enum implements IEnum {
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
return Struct.typesToMap(this.registry, typeMap);
|
||||
return typesToMap(this.registry, typeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { AnyJson, Codec, Registry } from '../types';
|
||||
|
||||
import { isFunction, stringify } from '@polkadot/util';
|
||||
|
||||
import { compareMap, defineProperty } from './utils';
|
||||
import { compareMap, defineProperties } from './utils';
|
||||
|
||||
/** @internal */
|
||||
function decodeJson (value?: Record<string, unknown> | null): [string, any][] {
|
||||
@@ -34,11 +34,7 @@ export class Json extends Map<string, any> implements Codec {
|
||||
|
||||
this.registry = registry;
|
||||
|
||||
for (let i = 0; i < decoded.length; i++) {
|
||||
const [key] = decoded[i];
|
||||
|
||||
defineProperty(this, key, () => this.get(key) as Codec);
|
||||
}
|
||||
defineProperties(this, decoded.map(([k]) => k), (k) => this.get(k));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Constructor, ISet, Registry } from '../types';
|
||||
|
||||
import { assert, BN, bnToBn, bnToU8a, isBn, isNumber, isString, isU8a, isUndefined, stringCamelCase, stringify, stringUpperFirst, u8aToBn, u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import { compareArray, defineProperty } from './utils';
|
||||
import { compareArray, defineProperties } from './utils';
|
||||
|
||||
type SetValues = Record<string, number | BN>;
|
||||
|
||||
@@ -104,17 +104,18 @@ export class CodecSet extends Set<string> implements ISet<string> {
|
||||
}
|
||||
|
||||
public static with (values: SetValues, bitLength?: number): Constructor<CodecSet> {
|
||||
const keys = Object.keys(values);
|
||||
const isKeys = new Array<string>(keys.length);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
isKeys[i] = `is${stringUpperFirst(stringCamelCase(keys[i]))}`;
|
||||
}
|
||||
|
||||
return class extends CodecSet {
|
||||
constructor (registry: Registry, value?: unknown) {
|
||||
super(registry, values, value as undefined, bitLength);
|
||||
|
||||
const keys = Object.keys(values);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
|
||||
defineProperty(this, `is${stringUpperFirst(stringCamelCase(key))}`, () => this.strings.includes(key));
|
||||
}
|
||||
defineProperties(this, isKeys, (_, i) => this.strings.includes(keys[i]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { AnyJson, BareOpts, Codec, Constructor, ConstructorDef, IStruct, Re
|
||||
|
||||
import { assert, hexToU8a, isBoolean, isFunction, isHex, isObject, isU8a, isUndefined, stringCamelCase, stringify, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { compareMap, decodeU8a, defineProperty, mapToTypeMap } from './utils';
|
||||
import { compareMap, decodeU8a, defineProperties, mapToTypeMap, typesToMap } from './utils';
|
||||
|
||||
type TypesDef<T = Codec> = Record<string, string | Constructor<T>>;
|
||||
|
||||
@@ -147,34 +147,17 @@ export class Struct<
|
||||
}
|
||||
|
||||
public static with<S extends TypesDef> (Types: S, jsonMap?: Map<string, string>): Constructor<Struct<S>> {
|
||||
const keys = Object.keys(Types);
|
||||
|
||||
return class extends Struct<S> {
|
||||
constructor (registry: Registry, value?: unknown) {
|
||||
super(registry, Types, value as HexString, jsonMap);
|
||||
|
||||
const keys = Object.keys(Types);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
|
||||
defineProperty(this, key, () => this.get(key as keyof S));
|
||||
}
|
||||
defineProperties(this, keys, (k) => this.get(k));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static typesToMap (registry: Registry, Types: Record<string, Constructor>): Record<string, string> {
|
||||
const entries = Object.entries(Types);
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [key, Type] = entries[i];
|
||||
|
||||
result[key] = registry.getClassName(Type) || new Type(registry).toRawType();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The available keys for this struct
|
||||
*/
|
||||
@@ -299,7 +282,7 @@ export class Struct<
|
||||
* @description Returns the base runtime type name for this instance
|
||||
*/
|
||||
public toRawType (): string {
|
||||
return stringify(Struct.typesToMap(this.registry, this.#Types));
|
||||
return stringify(typesToMap(this.registry, this.#Types));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { isUndefined } from '@polkadot/util';
|
||||
|
||||
const SKIP_KEYS = ['hash'];
|
||||
const SKIP_KEYS = ['entries', 'hash', 'keys', 'values'];
|
||||
|
||||
export function defineProperty (that: object, key: string, get: () => unknown): void {
|
||||
if (SKIP_KEYS.includes(key)) {
|
||||
@@ -16,3 +16,12 @@ export function defineProperty (that: object, key: string, get: () => unknown):
|
||||
Object.defineProperty(that, key, { enumerable: true, get });
|
||||
}
|
||||
}
|
||||
|
||||
export function defineProperties (that: object, keys: string[], get: (k: string, i: number) => unknown): void {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const index = i;
|
||||
|
||||
defineProperty(that, key, () => get(key, index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ export { compareArray } from './compareArray';
|
||||
export { compareMap } from './compareMap';
|
||||
export { compareSet } from './compareSet';
|
||||
export { decodeU8a, decodeU8aVec } from './decodeU8a';
|
||||
export { defineProperty } from './defineProperty';
|
||||
export { defineProperties, defineProperty } from './defineProperty';
|
||||
export { mapToTypeMap } from './mapToTypeMap';
|
||||
export { typeToConstructor } from './typeToConstructor';
|
||||
export { sortAsc, sortSet, sortMap } from './sortValues';
|
||||
export { typesToMap } from './typesToMap';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2021 @polkadot/types authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Constructor, Registry } from '../../types';
|
||||
|
||||
export function typesToMap (registry: Registry, Types: Record<string, Constructor>): Record<string, string> {
|
||||
const entries = Object.entries(Types);
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [key, Type] = entries[i];
|
||||
|
||||
result[key] = registry.getClassName(Type) || new Type(registry).toRawType();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user