Add type string generation to PortableRegistry (#3802)
* Add type string generation to PortableRegistry * createClass updates * Add missing field
This commit is contained in:
@@ -20,12 +20,12 @@
|
||||
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/types#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.6",
|
||||
"@polkadot/util": "^7.0.2",
|
||||
"@polkadot/util-crypto": "^7.0.2",
|
||||
"@polkadot/util": "^7.0.3-0",
|
||||
"@polkadot/util-crypto": "^7.0.3-0",
|
||||
"rxjs": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@polkadot/keyring": "^7.0.2",
|
||||
"@polkadot/keyring": "^7.0.3-0",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"bn.js": "^4.11.9"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { FromReg, TypeDef } from './types';
|
||||
import { assert, isNumber, isUndefined, stringify } from '@polkadot/util';
|
||||
|
||||
import { BTreeMap, BTreeSet, CodecSet, Compact, DoNotConstruct, Enum, HashMap, Int, Option, Result, Struct, Tuple, U8aFixed, UInt, Vec, VecFixed } from '../codec';
|
||||
import { Bytes, Null } from '../primitive';
|
||||
import { getTypeDef } from './getTypeDef';
|
||||
import { TypeDefInfo } from './types';
|
||||
|
||||
@@ -36,18 +37,6 @@ export function ClassOf<K extends keyof InterfaceTypes> (registry: Registry, nam
|
||||
return ClassOfUnsafe<Codec, K>(registry, name) as any;
|
||||
}
|
||||
|
||||
function expandLookup (registry: Registry, type: string): keyof InterfaceTypes {
|
||||
if (registry.isLookupType(type)) {
|
||||
const subType = registry.lookup.getTypeDef(type).type;
|
||||
|
||||
if (subType !== type) {
|
||||
return expandLookup(registry, subType);
|
||||
}
|
||||
}
|
||||
|
||||
return type as keyof InterfaceTypes;
|
||||
}
|
||||
|
||||
function getSubDefArray (value: TypeDef): TypeDef[] {
|
||||
assert(value.sub && Array.isArray(value.sub), () => `Expected subtype as TypeDef[] in ${stringify(value)}`);
|
||||
|
||||
@@ -142,7 +131,7 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
[TypeDefInfo.Null]: (registry: Registry, _: TypeDef): Constructor =>
|
||||
createClass(registry, 'Null'),
|
||||
Null,
|
||||
|
||||
[TypeDefInfo.Option]: (registry: Registry, value: TypeDef): Constructor =>
|
||||
Option.with(getSubType(value)),
|
||||
@@ -171,7 +160,7 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
},
|
||||
|
||||
[TypeDefInfo.Si]: (registry: Registry, value: TypeDef): Constructor =>
|
||||
getTypeClass(registry, registry.lookup.getTypeDef(value.index as number)),
|
||||
getTypeClass(registry, registry.lookup.getTypeDef(value.type)),
|
||||
|
||||
[TypeDefInfo.Struct]: (registry: Registry, value: TypeDef): Constructor =>
|
||||
Struct.with(getTypeClassMap(value), value.alias),
|
||||
@@ -183,11 +172,11 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
createInt(value, UInt),
|
||||
|
||||
[TypeDefInfo.Vec]: (registry: Registry, value: TypeDef): Constructor => {
|
||||
const subType = expandLookup(registry, getSubType(value));
|
||||
const subType = getSubType(value);
|
||||
|
||||
return (
|
||||
subType === 'u8'
|
||||
? createClass(registry, 'Bytes')
|
||||
? Bytes
|
||||
: Vec.with(subType)
|
||||
);
|
||||
},
|
||||
@@ -195,7 +184,7 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
[TypeDefInfo.VecFixed]: (registry: Registry, { displayName, length, sub }: TypeDef): Constructor => {
|
||||
assert(isNumber(length) && !isUndefined(sub), 'Expected length & type information for fixed vector');
|
||||
|
||||
const subType = expandLookup(registry, (sub as TypeDef).type);
|
||||
const subType = (sub as TypeDef).type as keyof InterfaceTypes;
|
||||
|
||||
return (
|
||||
subType === 'u8'
|
||||
@@ -206,25 +195,27 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
};
|
||||
|
||||
// Returns the type Class for construction
|
||||
export function getTypeClass<T extends Codec = Codec> (registry: Registry, value: TypeDef): Constructor<T> {
|
||||
let Type = registry.get<T>(value.type);
|
||||
export function getTypeClass<T extends Codec = Codec> (registry: Registry, typeDef: TypeDef): Constructor<T> {
|
||||
let Type = registry.get<T>(typeDef.type);
|
||||
|
||||
if (Type) {
|
||||
return Type;
|
||||
}
|
||||
|
||||
const getFn = infoMapping[value.info];
|
||||
try {
|
||||
Type = infoMapping[typeDef.info](registry, typeDef) as Constructor<T>;
|
||||
|
||||
assert(getFn, () => `Unable to construct class from ${stringify(value)}`);
|
||||
assert(Type, 'No class created');
|
||||
|
||||
Type = getFn(registry, value) as Constructor<T>;
|
||||
// don't clobber any existing
|
||||
if (!Type.__fallbackType && typeDef.fallbackType) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ...this is the only place we we actually assign this...
|
||||
Type.__fallbackType = typeDef.fallbackType;
|
||||
}
|
||||
|
||||
// don't clobber any existing
|
||||
if (!Type.__fallbackType && value.fallbackType) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ...this is the only place we we actually assign this...
|
||||
Type.__fallbackType = value.fallbackType;
|
||||
return Type;
|
||||
} catch (error) {
|
||||
throw new Error(`Unable to construct class from ${stringify(typeDef)}: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
return Type;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface TypeDef {
|
||||
displayName?: string;
|
||||
fallbackType?: string;
|
||||
info: TypeDefInfo;
|
||||
isFromSi?: boolean;
|
||||
index?: number;
|
||||
length?: number;
|
||||
name?: string;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
import type { Vec } from '../codec/Vec';
|
||||
import type { PortableType } from '../interfaces/metadata';
|
||||
import type { SiField, SiLookupTypeId, SiType, SiTypeDefArray, SiTypeDefCompact, SiTypeDefComposite, SiTypeDefSequence, SiTypeDefTuple, SiTypeDefVariant, SiVariant } from '../interfaces/scaleInfo';
|
||||
import type { Type } from '../primitive/Type';
|
||||
import type { Registry, TypeDef } from '../types';
|
||||
|
||||
import { assert, isNumber, isString, stringCamelCase, stringify } from '@polkadot/util';
|
||||
|
||||
import { Struct } from '../codec/Struct';
|
||||
import { withTypeString } from '../create/encodeTypes';
|
||||
import { getTypeDef } from '../create/getTypeDef';
|
||||
import { TypeDefInfo } from '../types';
|
||||
|
||||
@@ -62,17 +64,33 @@ export class GenericPortableRegistry extends Struct {
|
||||
const index = this.#getLookupId(lookupId);
|
||||
|
||||
if (!this.#typeDefs[index]) {
|
||||
this.#typeDefs[index] = this.#extract(this.getSiType(lookupId), index);
|
||||
// we set first since we will get into circular lookups along the way
|
||||
this.#typeDefs[index] = {
|
||||
info: TypeDefInfo.DoNotConstruct,
|
||||
type: this.registry.createLookupType(index)
|
||||
};
|
||||
|
||||
const typeDef = this.#extract(this.getSiType(lookupId), index);
|
||||
|
||||
Object.keys(typeDef).forEach((key): void => {
|
||||
// these are safe since we are looking through the keys as set
|
||||
this.#typeDefs[index][key as 'info'] = typeDef[key as 'info'];
|
||||
});
|
||||
}
|
||||
|
||||
return this.#typeDefs[index];
|
||||
}
|
||||
|
||||
#createSiDef (type: SiLookupTypeId): TypeDef {
|
||||
return {
|
||||
info: TypeDefInfo.Si,
|
||||
type: this.registry.createLookupType(type)
|
||||
};
|
||||
#createSiDef (lookupId: SiLookupTypeId): TypeDef {
|
||||
const typeDef = this.getTypeDef(lookupId);
|
||||
|
||||
// Flatten Plain types immediately, otherwise setup for a lookup
|
||||
return typeDef.info === TypeDefInfo.Plain
|
||||
? typeDef
|
||||
: {
|
||||
info: TypeDefInfo.Si,
|
||||
type: this.registry.createLookupType(lookupId)
|
||||
};
|
||||
}
|
||||
|
||||
#getLookupId (lookupId: SiLookupTypeId | string | number): number {
|
||||
@@ -112,7 +130,7 @@ export class GenericPortableRegistry extends Struct {
|
||||
} else if (type.def.isComposite) {
|
||||
typeDef = this.#extractComposite(lookupIndex, type.def.asComposite);
|
||||
} else if (type.def.isHistoricMetaCompat) {
|
||||
typeDef = getTypeDef(type.def.asHistoricMetaCompat);
|
||||
typeDef = this.#extractHistoric(lookupIndex, type.def.asHistoricMetaCompat);
|
||||
} else if (type.def.isPrimitive) {
|
||||
typeDef = this.#extractPrimitive(lookupIndex, type);
|
||||
} else if (type.def.isSequence) {
|
||||
@@ -143,30 +161,28 @@ export class GenericPortableRegistry extends Struct {
|
||||
};
|
||||
}
|
||||
|
||||
#extractArray (lookupIndex: number, { len: length, type }: SiTypeDefArray): TypeDef {
|
||||
#extractArray (_: number, { len: length, type }: SiTypeDefArray): TypeDef {
|
||||
assert(!length || length.toNumber() <= 256, 'PortableRegistry: Only support for [Type; <length>], where length <= 256');
|
||||
|
||||
return {
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.VecFixed,
|
||||
length: length.toNumber(),
|
||||
sub: this.#createSiDef(type),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
};
|
||||
sub: this.#createSiDef(type)
|
||||
});
|
||||
}
|
||||
|
||||
#extractCompact (lookupIndex: number, { type }: SiTypeDefCompact): TypeDef {
|
||||
return {
|
||||
#extractCompact (_: number, { type }: SiTypeDefCompact): TypeDef {
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Compact,
|
||||
sub: this.#createSiDef(type),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
};
|
||||
sub: this.#createSiDef(type)
|
||||
});
|
||||
}
|
||||
|
||||
#extractComposite (lookupIndex: number, { fields }: SiTypeDefComposite): TypeDef {
|
||||
return this.#extractFields(lookupIndex, fields);
|
||||
}
|
||||
|
||||
#extractFields (lookupIndex: number, fields: SiField[]): TypeDef {
|
||||
#extractFields (_: number, fields: SiField[]): TypeDef {
|
||||
const [isStruct, isTuple] = fields.reduce(([isAllNamed, isAllUnnamed], { name }) => ([
|
||||
isAllNamed && name.isSome,
|
||||
isAllUnnamed && name.isNone
|
||||
@@ -175,21 +191,19 @@ export class GenericPortableRegistry extends Struct {
|
||||
|
||||
assert(isTuple || isStruct, 'PortableRegistry: Invalid fields type detected, expected either Tuple or Struct');
|
||||
|
||||
if (isTuple) {
|
||||
if (fields.length === 0) {
|
||||
return {
|
||||
info: TypeDefInfo.Null,
|
||||
type: 'Null'
|
||||
};
|
||||
} else if (fields.length === 1) {
|
||||
return {
|
||||
...this.#createSiDef(fields[0].type),
|
||||
...(fields[0].name.isSome
|
||||
? { name: stringCamelCase(fields[0].name.unwrap()) }
|
||||
: {}
|
||||
)
|
||||
};
|
||||
}
|
||||
if (fields.length === 0) {
|
||||
return {
|
||||
info: TypeDefInfo.Null,
|
||||
type: 'Null'
|
||||
};
|
||||
} else if (isTuple && fields.length === 1) {
|
||||
return {
|
||||
...this.#createSiDef(fields[0].type),
|
||||
...(fields[0].name.isSome
|
||||
? { name: stringCamelCase(fields[0].name.unwrap()) }
|
||||
: {}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const sub = fields.map(({ name, type }) => ({
|
||||
@@ -200,16 +214,21 @@ export class GenericPortableRegistry extends Struct {
|
||||
)
|
||||
}));
|
||||
|
||||
return {
|
||||
return withTypeString(this.registry, {
|
||||
info: isTuple // Tuple check first
|
||||
? TypeDefInfo.Tuple
|
||||
: TypeDefInfo.Struct,
|
||||
sub,
|
||||
type: `(${sub.map(({ type }) => type).join(',')})`
|
||||
};
|
||||
sub
|
||||
});
|
||||
}
|
||||
|
||||
#extractPrimitive (lookupIndex: number, type: SiType): TypeDef {
|
||||
#extractHistoric (_: number, type: Type): TypeDef {
|
||||
const inner = getTypeDef(type);
|
||||
|
||||
return { ...inner, displayName: type.toString(), isFromSi: true };
|
||||
}
|
||||
|
||||
#extractPrimitive (_: number, type: SiType): TypeDef {
|
||||
const typeStr = type.def.asPrimitive.type.toString();
|
||||
|
||||
return {
|
||||
@@ -218,68 +237,89 @@ export class GenericPortableRegistry extends Struct {
|
||||
};
|
||||
}
|
||||
|
||||
#extractPrimitivePath (lookupIndex: number, type: SiType): TypeDef {
|
||||
#extractPrimitivePath (_: number, type: SiType): TypeDef {
|
||||
return {
|
||||
info: TypeDefInfo.Plain,
|
||||
type: type.path[type.path.length - 1].toString()
|
||||
};
|
||||
}
|
||||
|
||||
#extractSequence (lookupIndex: number, { type }: SiTypeDefSequence): TypeDef {
|
||||
return {
|
||||
#extractSequence (_: number, { type }: SiTypeDefSequence): TypeDef {
|
||||
const sub = this.#createSiDef(type);
|
||||
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Vec,
|
||||
sub: this.#createSiDef(type),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
};
|
||||
sub
|
||||
});
|
||||
}
|
||||
|
||||
#extractTuple (lookupIndex: number, ids: SiTypeDefTuple): TypeDef {
|
||||
return ids.length === 1
|
||||
? this.getTypeDef(ids[0])
|
||||
: {
|
||||
info: TypeDefInfo.Tuple,
|
||||
sub: ids.map((type) => this.#createSiDef(type)),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
};
|
||||
#extractTuple (_: number, ids: SiTypeDefTuple): TypeDef {
|
||||
if (ids.length === 1) {
|
||||
return this.getTypeDef(ids[0]);
|
||||
}
|
||||
|
||||
const sub = ids.map((type) => this.#createSiDef(type));
|
||||
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Tuple,
|
||||
sub
|
||||
});
|
||||
}
|
||||
|
||||
#extractVariant (lookupIndex: number, { params, path }: SiType, { variants }: SiTypeDefVariant): TypeDef {
|
||||
const specialVariant = path[0].toString();
|
||||
|
||||
return specialVariant === 'Option'
|
||||
? {
|
||||
if (specialVariant === 'Option') {
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Option,
|
||||
sub: this.#createSiDef(params[0].type.unwrap()),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
}
|
||||
: specialVariant === 'Result'
|
||||
? {
|
||||
info: TypeDefInfo.Result,
|
||||
sub: params.map((p, index) => ({
|
||||
name: ['Ok', 'Error'][index],
|
||||
...this.#createSiDef(p.type.unwrap())
|
||||
})),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
}
|
||||
: {
|
||||
info: TypeDefInfo.Enum,
|
||||
sub: this.#extractVariantSub(lookupIndex, variants),
|
||||
type: this.registry.createLookupType(lookupIndex)
|
||||
};
|
||||
sub: this.#createSiDef(params[0].type.unwrap())
|
||||
});
|
||||
} else if (specialVariant === 'Result') {
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Result,
|
||||
sub: params.map((p) => this.#createSiDef(p.type.unwrap())).map((def, index) => ({
|
||||
name: ['Ok', 'Error'][index],
|
||||
...def
|
||||
}))
|
||||
});
|
||||
} else if (variants.length === 0) {
|
||||
return {
|
||||
info: TypeDefInfo.Null,
|
||||
type: 'Null'
|
||||
};
|
||||
}
|
||||
|
||||
return this.#extractVariantEnum(lookupIndex, variants);
|
||||
}
|
||||
|
||||
#extractVariantSub (lookupIndex: number, variants: SiVariant[]): TypeDef[] {
|
||||
return variants.every(({ fields }) => fields.length === 0)
|
||||
? variants.map(({ index, name }) => ({
|
||||
index: index.toNumber(),
|
||||
info: TypeDefInfo.Plain,
|
||||
name: name.toString(),
|
||||
type: 'Null'
|
||||
}))
|
||||
: variants.map(({ fields, index, name }) => ({
|
||||
...this.#extractFields(lookupIndex, fields),
|
||||
index: index.toNumber(),
|
||||
name: name.toString()
|
||||
}));
|
||||
#extractVariantEnum (lookupIndex: number, variants: SiVariant[]): TypeDef {
|
||||
const sub: (TypeDef & { name: string })[] = [];
|
||||
|
||||
// we may get entries out of order, arrange them first before creating with gaps filled
|
||||
variants
|
||||
.sort((a, b) => a.index.cmp(b.index))
|
||||
.forEach(({ fields, index, name }) => {
|
||||
const desired = index.toNumber();
|
||||
|
||||
while (sub.length !== desired) {
|
||||
sub.push({
|
||||
index: sub.length,
|
||||
info: TypeDefInfo.Null,
|
||||
name: `Unused${sub.length}`,
|
||||
type: 'Null'
|
||||
});
|
||||
}
|
||||
|
||||
sub.push({
|
||||
...this.#extractFields(lookupIndex, fields),
|
||||
index: index.toNumber(),
|
||||
name: name.toString()
|
||||
});
|
||||
});
|
||||
|
||||
return withTypeString(this.registry, {
|
||||
info: TypeDefInfo.Enum,
|
||||
sub
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user