// Copyright 2017-2020 @polkadot/api-contract authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. import { MetaRegistryItem, MetaRegistryJson, MetaTypeDefClikeEnum, MetaType, MetaTypeDefEnum, MetaTypeDefEnumVariant, MetaTypeDefEnumVariantStruct, MetaTypeDefEnumVariantTupleStruct, MetaTypeDefEnumVariantUnit, MetaTypeDefStruct, MetaTypeDefStructField, MetaTypeDefTupleStruct, MetaTypeDefUnion, MetaTypeIdCustom, MetaTypeIdVec, MetaTypeIdVecFixed, MetaTypeInfo, Registry, StringIndex, TypeDef, TypeDefInfo, TypeIndex } from '@polkadot/types/types'; import { assert } from '@polkadot/util'; import { displayType, withTypeString } from '@polkadot/types'; const builtinMap: [(id: unknown | unknown[] | { 'array.type': unknown } | { 'slice.type': unknown }) => boolean, MetaTypeInfo][] = [ [(id: any) => typeof id === 'string', MetaTypeInfo.BuiltinPlain], [(id: any) => Array.isArray(id), MetaTypeInfo.BuiltinTuple], [(id: any) => !!((id as { 'array.type': unknown })['array.type']), MetaTypeInfo.BuiltinVecFixed], [(id: any) => !!((id as { 'slice.type': unknown })['slice.type']), MetaTypeInfo.BuiltinVec] ]; const typeMap: [string, MetaTypeInfo][] = [ ['enum.variants', MetaTypeInfo.Enum], ['clike_enum.variants', MetaTypeInfo.ClikeEnum], ['struct.fields', MetaTypeInfo.Struct], ['tuple_struct.types', MetaTypeInfo.TupleStruct] ]; function detectedType ({ def, id }: MetaType): MetaTypeInfo { assert(!(def as MetaTypeDefUnion)['union.fields'], 'Invalid union type definition found'); const lookup = def === 'builtin' ? builtinMap.find(([test]) => test(id)) : typeMap.find(([test]) => !!(def as unknown as Record)[test]); return lookup ? lookup[1] : MetaTypeInfo.Null; } class MetadataRegistryLookup { public readonly registry: Registry; protected _strings: string[] = []; protected _types: MetaType[] = []; public typeDefs: TypeDef[] = []; constructor (registry: Registry, { registry: { strings, types } }: MetaRegistryJson) { this.registry = registry; this._strings = strings; this._types = types; } protected _hasItemAt (index: number, variant: MetaRegistryItem): boolean { switch (variant) { case MetaRegistryItem.String: return this._strings && !!this._strings[index - 1]; case MetaRegistryItem.Type: return this._types && !!this._types[index - 1]; case MetaRegistryItem.TypeDef: return this.typeDefs && !!this.typeDefs[index - 1]; } } protected _itemAt (index: number, variant: MetaRegistryItem): string | MetaType | TypeDef { assert(this._hasItemAt(index, variant), `MetaRegistry: Invalid ${variant} index ${index} found in metadata`); switch (variant) { case MetaRegistryItem.String: return this._strings[index - 1]; case MetaRegistryItem.Type: return this._types[index - 1]; case MetaRegistryItem.TypeDef: return this.typeDefs[index - 1]; } } protected _itemsAt (indices: number[], variant: MetaRegistryItem): (string | MetaType | TypeDef)[] { return indices.map((index: number) => this._itemAt(index, variant)); } protected _stringAt (index: StringIndex): string { return this._itemAt(index, MetaRegistryItem.String) as string; } public stringsAt (indices: StringIndex[]): string[] { return this._itemsAt(indices, MetaRegistryItem.String) as string[]; } public typeAt (index: TypeIndex): MetaType { return this._itemAt(index, MetaRegistryItem.Type) as MetaType; } public typesAt (indices: TypeIndex[]): MetaType[] { return this._itemsAt(indices, MetaRegistryItem.Type) as MetaType[]; } public hasTypeDefAt (index: TypeIndex): boolean { return this._hasItemAt(index, MetaRegistryItem.TypeDef); } public typeDefAt (index: TypeIndex, extra: Pick = {}): TypeDef { return { ...this._itemAt(index, MetaRegistryItem.TypeDef) as TypeDef, ...extra }; } } export default class MetaRegistry extends MetadataRegistryLookup { constructor (registry: Registry, json: MetaRegistryJson) { super(registry, json); // Generate TypeDefs for each provided registry type this._types.forEach((_, index: number): void => this.setTypeDefAtIndex(index + 1)); } public setTypeDefAtIndex (typeIndex: TypeIndex): void { this.typeDefs[typeIndex - 1] = this.typeDefFromMetaType(this.typeAt(typeIndex), typeIndex); } private _typeDefIdFields ({ id }: MetaType): Pick { const { 'custom.name': nameIndex, 'custom.namespace': namespaceIndices, 'custom.params': paramsIndices } = id as MetaTypeIdCustom; if (!nameIndex) { return {}; } return { name: this._stringAt(nameIndex), ...(namespaceIndices && namespaceIndices.length ? { namespace: this.stringsAt(namespaceIndices).join('::') } : {} ), ...(paramsIndices && paramsIndices.length ? { params: this.typesAt(paramsIndices).map((type): TypeDef => this.typeDefFromMetaType(type)) } : {} ) }; } private _typeDefDefFields (metaType: MetaType, typeIndex: TypeIndex = -1): Pick { let typeDef; switch (detectedType(metaType)) { case MetaTypeInfo.BuiltinPlain: typeDef = this._typeDefForBuiltinPlain(metaType.id as string); break; case MetaTypeInfo.BuiltinTuple: typeDef = this._typeDefForBuiltinTuple(metaType.id as TypeIndex[]); break; case MetaTypeInfo.BuiltinVec: typeDef = this._typeDefForBuiltinVec(metaType.id as MetaTypeIdVec, typeIndex); break; case MetaTypeInfo.BuiltinVecFixed: typeDef = this._typeDefForBuiltinVecFixed(metaType.id as MetaTypeIdVecFixed, typeIndex); break; case MetaTypeInfo.Enum: typeDef = this._typeDefForEnum(metaType.def as MetaTypeDefEnum, metaType.id as MetaTypeIdCustom, typeIndex); break; case MetaTypeInfo.ClikeEnum: typeDef = this._typeDefForClikeEnum(metaType.def as MetaTypeDefClikeEnum); break; case MetaTypeInfo.Struct: typeDef = this.typeDefForStruct(metaType.def as MetaTypeDefStruct); break; case MetaTypeInfo.TupleStruct: typeDef = this._typeDefForTupleStruct(metaType.def as MetaTypeDefTupleStruct); break; case MetaTypeInfo.Null: default: throw new Error(`Invalid type detected at index ${typeIndex}`); } return typeDef; } public typeDefFromMetaType (metaType: MetaType, typeIndex?: TypeIndex): TypeDef { return withTypeString({ info: TypeDefInfo.Null, type: '', ...this._typeDefDefFields(metaType, typeIndex), ...this._typeDefIdFields(metaType) }) as TypeDef; } public typeDefFromMetaTypeAt (typeIndex: TypeIndex): TypeDef { if (!this.hasTypeDefAt(typeIndex)) { this.setTypeDefAtIndex(typeIndex); } return this.typeDefAt(typeIndex); } private _typeDefForEnumVariant (variant: MetaTypeDefEnumVariant): Pick { const { 'unit_variant.name': unitNameIndex } = variant as MetaTypeDefEnumVariantUnit; if (unitNameIndex) { return { info: TypeDefInfo.Plain, name: this._stringAt(unitNameIndex), type: 'Null' }; } const { 'tuple_struct_variant.name': tupleStructVariantNameIndex } = variant as MetaTypeDefEnumVariantTupleStruct; if (tupleStructVariantNameIndex) { return this._typeDefForTupleStruct(variant as MetaTypeDefEnumVariantTupleStruct); } const { 'struct_variant.name': structVariantNameIndex } = variant as MetaTypeDefEnumVariantStruct; if (structVariantNameIndex) { return this.typeDefForStruct(variant as MetaTypeDefEnumVariantStruct); } return { info: TypeDefInfo.Null, type: 'Null' }; } private _typeDefForBuiltinPlain (id: string): Pick { return { info: TypeDefInfo.Plain, type: id }; } private _typeDefForBuiltinTuple (id: TypeIndex[]): Pick { const sub = id.map((tupleTypeIndex: number): TypeDef => this.typeDefFromMetaTypeAt(tupleTypeIndex)); return { info: TypeDefInfo.Tuple, sub }; } private _typeDefForBuiltinVec (id: MetaTypeIdVec, typeIndex: TypeIndex = -1): Pick { const { 'slice.type': vecTypeIndex } = id; assert(!typeIndex || vecTypeIndex !== typeIndex, `MetaRegistry: self-referencing registry type at index ${typeIndex}`); const type = displayType(this.typeDefFromMetaTypeAt(vecTypeIndex)); assert(type && type.length > 0, `MetaRegistry: Invalid builtin Vec type found at index ${typeIndex}`); return { info: TypeDefInfo.Vec, sub: this.typeDefFromMetaTypeAt(vecTypeIndex), type: `Vec<${type}>` }; } private _typeDefForBuiltinVecFixed (id: MetaTypeIdVecFixed, typeIndex: TypeIndex = -1): Pick { const { 'array.len': vecLength, 'array.type': vecTypeIndex } = id; assert(!vecLength || vecLength <= 256, 'MetaRegistry: Only support for [Type; ], where length <= 256'); assert(!typeIndex || vecTypeIndex !== typeIndex, `MetaRegistry: self-referencing registry type at index ${typeIndex}`); const type = displayType(this.typeDefFromMetaTypeAt(vecTypeIndex)); assert(type && type.length > 0, `MetaRegistry: Invalid vector type found at index ${typeIndex}`); return { info: TypeDefInfo.VecFixed, length: vecLength, // ex: { type: type } sub: this.typeDefFromMetaTypeAt(vecTypeIndex), type: `[${type};${vecLength}]` }; } private _typeDefForEnum (def: MetaTypeDefEnum, id: MetaTypeIdCustom, typeIndex?: TypeIndex): Pick { const name = id && this._stringAt(id['custom.name']); switch (name) { case 'Option': return this.typeDefForOption(id, typeIndex); case 'Result': return this.typeDefForResult(id, typeIndex); default: { const sub = def['enum.variants'].map((variant) => this._typeDefForEnumVariant(variant)); return { info: TypeDefInfo.Enum, sub }; } } } private _typeDefForClikeEnum (def: MetaTypeDefClikeEnum): Pick { return { info: TypeDefInfo.Enum, sub: def['clike_enum.variants'].map(({ discriminant, name: nameIndex }): TypeDef => ({ ext: { discriminant }, info: TypeDefInfo.Plain, name: this._stringAt(nameIndex), type: 'Null' })) }; } public typeDefForOption (id: MetaTypeIdCustom, typeIndex: TypeIndex = -1): Pick { assert(id['custom.params'] && id['custom.params'][0], `Invalid Option type defined at index ${typeIndex}`); return { info: TypeDefInfo.Option, sub: this.typeDefFromMetaTypeAt(id['custom.params']![0]) }; } public typeDefForResult (id: MetaTypeIdCustom, typeIndex: TypeIndex = -1): Pick { assert(id['custom.params'] && id['custom.params'][0] && id['custom.params'][1], `Invalid Result type defined at index ${typeIndex}`); return { info: TypeDefInfo.Result, sub: id['custom.params']!.map((typeIndex): TypeDef => this.typeDefFromMetaTypeAt(typeIndex)) }; } public typeDefForStruct (def: MetaTypeDefStruct | MetaTypeDefEnumVariantStruct): Pick { const structFields = (def as MetaTypeDefStruct)['struct.fields'] || (def as MetaTypeDefEnumVariantStruct)['struct_variant.fields']; const structNameIndex = (def as MetaTypeDefEnumVariantStruct)['struct_variant.name']; return withTypeString({ info: TypeDefInfo.Struct, ...(structNameIndex ? { name: this._stringAt(structNameIndex) } : {} ), sub: structFields.map((field: MetaTypeDefStructField): TypeDef => ({ ...this.typeDefFromMetaTypeAt(field.type), name: this._stringAt(field.name) })) }); } private _typeDefForTupleStruct (def: MetaTypeDefTupleStruct | MetaTypeDefEnumVariantTupleStruct): Pick { const tupleStructTypes = (def as MetaTypeDefTupleStruct)['tuple_struct.types'] || (def as MetaTypeDefEnumVariantTupleStruct)['tuple_struct_variant.types']; const tupleStructNameIndex = (def as MetaTypeDefEnumVariantTupleStruct)['tuple_struct_variant.name']; return withTypeString({ info: TypeDefInfo.Tuple, ...(tupleStructNameIndex ? { name: this._stringAt(tupleStructNameIndex) } : {} ), sub: tupleStructTypes.map((fieldIndex: number, index: number): TypeDef => ({ ...this.typeDefFromMetaTypeAt(fieldIndex), index })) }); } }