Rework Unconstructable type (#1888)
* Rework Unconstructable type * hasType updates * Unknown -> Unconstructable (as it was) * Remove commented test * class name * getOrThrow tests * typo. * getOrUnknown
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Codec, Constructor } from '../types';
|
||||
import { TypeDefInfo } from '../types';
|
||||
|
||||
import { createClass, getTypeClass, TypeRegistry } from '.';
|
||||
|
||||
@@ -27,9 +27,12 @@ describe('createClass', (): void => {
|
||||
describe('getTypeClass', (): void => {
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
it('does not allow invalid types', (): void => {
|
||||
expect(
|
||||
(): Constructor<Codec> => getTypeClass(registry, 'SomethingInvalid' as any)
|
||||
).toThrow(/determine type/);
|
||||
it('warns on invalid types', (): void => {
|
||||
const spy = jest.spyOn(console, 'warn');
|
||||
const typeDef = { info: TypeDefInfo.Plain, type: 'ABC' };
|
||||
|
||||
getTypeClass(registry, typeDef);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('Unable to resolve type ABC, it will fail on constrution');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
|
||||
[TypeDefInfo.Option]: (registry: Registry, value: TypeDef): Constructor => Option.with(getSubType(value)),
|
||||
|
||||
[TypeDefInfo.Plain]: (registry: Registry, value: TypeDef): Constructor =>
|
||||
registry.getOrThrow(value.type, `Unable to find plain type for ${JSON.stringify(value)}`),
|
||||
registry.getOrUnknown(value.type),
|
||||
|
||||
[TypeDefInfo.Result]: (registry: Registry, value: TypeDef): Constructor => {
|
||||
const [Ok, Error] = getTypeClassArray(value);
|
||||
@@ -166,7 +166,7 @@ export function getTypeClass<T extends Codec = Codec> (registry: Registry, value
|
||||
const getFn = infoMapping[value.info];
|
||||
|
||||
if (!getFn) {
|
||||
throw new Error(`Unable to determine type from ${JSON.stringify(value)}`);
|
||||
throw new Error(`Unable to construct class from ${JSON.stringify(value)}`);
|
||||
}
|
||||
|
||||
return getFn(registry, value) as Constructor<T>;
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Constructor } from '../types';
|
||||
import { Codec, Constructor } from '../types';
|
||||
|
||||
import Struct from '../codec/Struct';
|
||||
import Text from '../primitive/Text';
|
||||
import U32 from '../primitive/U32';
|
||||
import Unconstructable from '../primitive/Unconstructable';
|
||||
import { TypeRegistry } from './registry';
|
||||
|
||||
// Copied from interfacesTs
|
||||
@@ -24,6 +25,19 @@ describe('TypeRegistry', (): void => {
|
||||
expect(registry.get('non-exist')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws on non-existent via getOrThrow', (): void => {
|
||||
expect((): Constructor<Codec> => registry.getOrThrow('non-exist')).toThrow('type non-exist not found');
|
||||
expect((): Constructor<Codec> => registry.getOrThrow('non-exist', 'foo bar blah')).toThrow('foo bar blah');
|
||||
});
|
||||
|
||||
it('handles non exist type as Unknown (via getOrUnknown)', (): void => {
|
||||
const Type = registry.getOrUnknown('non-exist');
|
||||
|
||||
expect(Type).toBeDefined();
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
expect(isChildClass(Unconstructable, Type));
|
||||
});
|
||||
|
||||
it('can register single type', (): void => {
|
||||
registry.register(Text);
|
||||
expect(registry.get('Text')).toBe(Text);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { assert, formatBalance, isFunction, isString, isU8a, isUndefined, string
|
||||
import Raw from '../codec/Raw';
|
||||
import { EventData } from '../primitive/Generic/Event';
|
||||
import { defaultExtensions, expandExtensionTypes, findUnknownExtensions } from '../primitive/Extrinsic/signedExtensions';
|
||||
import Unconstructable from '../primitive/Unconstructable';
|
||||
import { createClass, getTypeClass } from './createClass';
|
||||
import { getTypeDef } from './getTypeDef';
|
||||
|
||||
@@ -90,6 +91,8 @@ export class TypeRegistry implements Registry {
|
||||
|
||||
private _metadataExtensions: string[] = defaultExtensions;
|
||||
|
||||
private _unknownTypes: Map<string, boolean> = new Map();
|
||||
|
||||
constructor () {
|
||||
// we only want to import these on creation, i.e. we want to avoid types
|
||||
// weird side-effects from circular references. (Since registry is injected
|
||||
@@ -157,17 +160,25 @@ export class TypeRegistry implements Registry {
|
||||
return Event;
|
||||
}
|
||||
|
||||
public get <T extends Codec = Codec> (name: string): Constructor<T> | undefined {
|
||||
public get <T extends Codec = Codec> (name: string, withUnknown?: boolean): Constructor<T> | undefined {
|
||||
let Type = this._classes.get(name);
|
||||
|
||||
// we have not already created the type, attempt it
|
||||
if (!Type) {
|
||||
const definition = this._definitions.get(name);
|
||||
let BaseType: Constructor<Codec> | undefined;
|
||||
|
||||
// we have a definition, so create the class now (lazily)
|
||||
if (definition) {
|
||||
const BaseType = createClass(this, definition);
|
||||
BaseType = createClass(this, definition);
|
||||
} else if (withUnknown) {
|
||||
console.warn(`Unable to resolve type ${name}, it will fail on constrution`);
|
||||
|
||||
this._unknownTypes.set(name, true);
|
||||
BaseType = Unconstructable.with(name);
|
||||
}
|
||||
|
||||
if (BaseType) {
|
||||
// NOTE If we didn't extend here, we would have strange artifacts. An example is
|
||||
// Balance, with this, new Balance() instanceof u128 is true, but Balance !== u128
|
||||
// Additionally, we now pass through the registry, which is a link to ourselves
|
||||
@@ -189,13 +200,17 @@ export class TypeRegistry implements Registry {
|
||||
}
|
||||
|
||||
public getOrThrow <T extends Codec = Codec> (name: string, msg?: string): Constructor<T> {
|
||||
const type = this.get<T>(name);
|
||||
const Type = this.get<T>(name);
|
||||
|
||||
if (isUndefined(type)) {
|
||||
if (isUndefined(Type)) {
|
||||
throw new Error(msg || `type ${name} not found`);
|
||||
}
|
||||
|
||||
return type;
|
||||
return Type;
|
||||
}
|
||||
|
||||
public getOrUnknown <T extends Codec = Codec> (name: string): Constructor<T> {
|
||||
return this.get<T>(name, true) as Constructor<T>;
|
||||
}
|
||||
|
||||
public getSignedExtensionExtra (): Record<string, InterfaceTypes> {
|
||||
@@ -215,7 +230,7 @@ export class TypeRegistry implements Registry {
|
||||
}
|
||||
|
||||
public hasType (name: string): boolean {
|
||||
return this.hasClass(name) || this.hasDef(name);
|
||||
return !this._unknownTypes.get(name) && (this.hasClass(name) || this.hasDef(name));
|
||||
}
|
||||
|
||||
public register (type: Constructor | RegistryTypes): void;
|
||||
|
||||
@@ -18,6 +18,7 @@ const UNCONSTRUCTABLE = [
|
||||
'ExtrinsicPayloadUnknown', 'GenericExtrinsicPayloadUnknown',
|
||||
'ExtrinsicUnknown', 'GenericExtrinsicUnknown',
|
||||
'GenericOrigin', 'Origin',
|
||||
'Unconstructable',
|
||||
'usize'
|
||||
].map((v): string => v.toLowerCase());
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
|
||||
import { Compact, Option, Raw, Vec } from '@polkadot/types/codec';
|
||||
import { BitVec, Bytes, Data, Fixed64, H160, H256, H512, IdentityFields, Null, StorageData, StorageKey, Text, Type, U256, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types/primitive';
|
||||
import { BitVec, Bytes, Data, Fixed64, H160, H256, H512, IdentityFields, Null, StorageData, StorageKey, Text, Type, U256, Unconstructable, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types/primitive';
|
||||
import { AccountId, AccountIdOf, AccountIndex, Address, AssetId, Balance, BalanceOf, Block, BlockNumber, Call, ChangesTrieConfiguration, Consensus, ConsensusEngineId, Digest, DigestItem, DispatchClass, DispatchInfo, DispatchInfoTo190, EcdsaSignature, Ed25519Signature, Extrinsic, ExtrinsicEra, ExtrinsicPayload, ExtrinsicPayloadUnknown, ExtrinsicPayloadV1, ExtrinsicPayloadV2, ExtrinsicPayloadV3, ExtrinsicPayloadV4, ExtrinsicSignatureV1, ExtrinsicSignatureV2, ExtrinsicSignatureV3, ExtrinsicSignatureV4, ExtrinsicUnknown, ExtrinsicV1, ExtrinsicV2, ExtrinsicV3, ExtrinsicV4, Hash, Header, ImmortalEra, Index, Justification, KeyTypeId, KeyValue, LockIdentifier, LookupSource, LookupTarget, Moment, MortalEra, MultiSignature, Origin, Perbill, Percent, Permill, Perquintill, Phantom, PhantomData, PreRuntime, Seal, SealV0, Signature, SignedBlock, SignerPayload, Sr25519Signature, ValidatorId, Weight, WeightMultiplier } from '@polkadot/types/interfaces/runtime';
|
||||
import { InclusionHeight, Uncle, UncleEntryItem } from '@polkadot/types/interfaces/authorship';
|
||||
import { RawAuraPreDigest } from '@polkadot/types/interfaces/aura';
|
||||
@@ -124,6 +124,9 @@ export interface InterfaceRegistry {
|
||||
'Compact<U256>': Compact<U256>;
|
||||
'Option<U256>': Option<U256>;
|
||||
'Vec<U256>': Vec<U256>;
|
||||
Unconstructable: Unconstructable;
|
||||
'Option<Unconstructable>': Option<Unconstructable>;
|
||||
'Vec<Unconstructable>': Vec<Unconstructable>;
|
||||
usize: usize;
|
||||
'Compact<usize>': Compact<usize>;
|
||||
'Option<usize>': Option<usize>;
|
||||
|
||||
@@ -9,18 +9,20 @@ import Null from './Null';
|
||||
/**
|
||||
* @name Unconstructable
|
||||
* @description
|
||||
* A type that should not be constructed
|
||||
* An unknown type theat fails on constrction with the type info
|
||||
*/
|
||||
export default class Unconstructable extends Null {
|
||||
constructor (registry: Registry) {
|
||||
constructor (registry: Registry, typeName: string) {
|
||||
super(registry);
|
||||
|
||||
throw new Error('Unconstructable should not be constructed, it is only a placeholder for compatibility');
|
||||
throw new Error(`Cannot construct unknown type ${typeName}`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public static with (typeDef: any): Constructor {
|
||||
public static with (typeName: string): Constructor {
|
||||
return class extends Unconstructable {
|
||||
constructor (registry: Registry) {
|
||||
super(registry, typeName);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export { default as u32 } from './U32';
|
||||
export { default as u64 } from './U64';
|
||||
export { default as u128 } from './U128';
|
||||
export { default as u256, default as U256 } from './U256';
|
||||
export { default as Unconstructable } from './Unconstructable';
|
||||
export { default as usize } from './USize';
|
||||
|
||||
export * from './Generic';
|
||||
|
||||
@@ -92,10 +92,11 @@ export interface Registry {
|
||||
// keep this as a generic Codec, however the actual impl. returns the correct
|
||||
findMetaEvent (eventIndex: Uint8Array): Constructor<any>;
|
||||
|
||||
get <T extends Codec = Codec> (name: string): Constructor<T> | undefined;
|
||||
get <T extends Codec = Codec> (name: string, withUnknown?: boolean): Constructor<T> | undefined;
|
||||
getChainProperties (): ChainProperties | undefined;
|
||||
getDefinition (name: string): string | undefined;
|
||||
getOrThrow <T extends Codec = Codec> (name: string, msg?: string): Constructor<T>;
|
||||
getOrUnknown <T extends Codec = Codec> (name: string): Constructor<T>;
|
||||
getSignedExtensionExtra (): Record<string, InterfaceTypes>;
|
||||
getSignedExtensionTypes (): Record<string, InterfaceTypes>;
|
||||
hasClass (name: string): boolean;
|
||||
|
||||
Reference in New Issue
Block a user