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:
Jaco Greeff
2020-02-18 13:35:42 +01:00
committed by GitHub
parent e984c74ca6
commit ad2939cc4f
10 changed files with 70 additions and 30 deletions
@@ -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');
});
});
+2 -2
View File
@@ -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>;
+15 -1
View File
@@ -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);
+21 -6
View File
@@ -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;
+1
View File
@@ -18,6 +18,7 @@ const UNCONSTRUCTABLE = [
'ExtrinsicPayloadUnknown', 'GenericExtrinsicPayloadUnknown',
'ExtrinsicUnknown', 'GenericExtrinsicUnknown',
'GenericOrigin', 'Origin',
'Unconstructable',
'usize'
].map((v): string => v.toLowerCase());
+4 -1
View File
@@ -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);
}
};
}
}
+1
View File
@@ -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';
+2 -1
View File
@@ -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;