Support and return default values (#547)
* Support and return default values * e2e tests for queries, with and without defaults * remove .only * Remove unused Codec import * Remove commented import * Add default creation test * Test creation with empty Bytes * Default storage test * Remove non-existent throw test
This commit is contained in:
@@ -2,6 +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 BN from 'bn.js';
|
||||
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||
|
||||
import Api from '../../src/promise';
|
||||
@@ -34,7 +35,7 @@ describe.skip('e2e queries', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('subscribes to queries', (done) => {
|
||||
it('subscribes to rpc', (done) => {
|
||||
api.rpc.chain.subscribeNewHead((header) => {
|
||||
expect(header.blockNumber.isZero()).toBe(false);
|
||||
|
||||
@@ -42,6 +43,22 @@ describe.skip('e2e queries', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes to queries', (done) => {
|
||||
api.query.system.accountNonce(keyring.ferdie.address(), (nonce) => {
|
||||
expect(nonce instanceof BN).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('subscribes to queries (default)', (done) => {
|
||||
api.query.staking.validatorPreferences(keyring.ferdie.address(), (prefs) => {
|
||||
expect(prefs.unstakeThreshold.toNumber()).toBe(12);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('makes a query at a specific block', async () => {
|
||||
const header = await api.rpc.chain.getHeader();
|
||||
const events = await api.query.system.events.at(header.hash);
|
||||
|
||||
@@ -208,8 +208,10 @@ export default class Rpc implements RpcInterface {
|
||||
!item
|
||||
? undefined
|
||||
: (
|
||||
// for `null` we fallback to the default value, or create an empty type,
|
||||
// otherwise we return the actual value as retrieved
|
||||
item.value.isNone
|
||||
? null
|
||||
? createType(type, (_key.meta || { default: undefined }).default)
|
||||
: createType(type, item.value.unwrap())
|
||||
)
|
||||
);
|
||||
|
||||
@@ -16,11 +16,11 @@ import U8a from './codec/U8a';
|
||||
* as what is found in [[Text]] and [[Vector]])
|
||||
*/
|
||||
export default class Bytes extends U8a {
|
||||
constructor (value: AnyU8a) {
|
||||
constructor (value?: AnyU8a) {
|
||||
super(Bytes.decodeBytes(value));
|
||||
}
|
||||
|
||||
private static decodeBytes (value: AnyU8a): Uint8Array {
|
||||
private static decodeBytes (value?: AnyU8a): Uint8Array | undefined {
|
||||
// FIXME Cyclic dependency, however needed for the StoreageData check below. In a perfect
|
||||
// world, we should probably be checking Bytes - however as a first step, check against
|
||||
// StorageData to cater for the _specific_ problematic case
|
||||
|
||||
@@ -12,6 +12,7 @@ import U8aFixed from './codec/U8aFixed';
|
||||
import { TypeDef, getTypeClass, getTypeDef } from './codec/createType';
|
||||
import Metadata from './Metadata';
|
||||
import { EventMetadata } from './Metadata/Events';
|
||||
import Null from './Null';
|
||||
|
||||
const EventTypes: { [index: string]: Constructor<EventData> } = {};
|
||||
|
||||
@@ -85,7 +86,7 @@ export class EventIndex extends U8aFixed {
|
||||
export default class Event extends Struct {
|
||||
// Currently we _only_ decode from Uint8Array, since we expect it to
|
||||
// be used via EventRecord
|
||||
constructor (_value: Uint8Array) {
|
||||
constructor (_value?: Uint8Array) {
|
||||
const { DataType, value } = Event.decodeEvent(_value);
|
||||
|
||||
super({
|
||||
@@ -94,12 +95,17 @@ export default class Event extends Struct {
|
||||
}, value);
|
||||
}
|
||||
|
||||
static decodeEvent (value: Uint8Array) {
|
||||
static decodeEvent (value: Uint8Array = new Uint8Array()) {
|
||||
const index = value.subarray(0, 2);
|
||||
const DataType = EventTypes[index.toString()];
|
||||
|
||||
if (isUndefined(DataType)) {
|
||||
throw new Error(`Unable to decode event for index ${u8aToHex(index)}`);
|
||||
console.error(`Unable to decode event for index ${u8aToHex(index)}`);
|
||||
|
||||
return {
|
||||
DataType: Null,
|
||||
value: undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,19 +2,44 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata';
|
||||
|
||||
import createType from '../codec/createType';
|
||||
import Method from '../Method';
|
||||
|
||||
import Metadata from './index';
|
||||
import latestParsed from './latest.substrate.json';
|
||||
import rpcData from './static';
|
||||
|
||||
describe('Metadata', () => {
|
||||
it('decodes latest properly', () => {
|
||||
const decoded = new Metadata(rpcData);
|
||||
const str = JSON.stringify(decoded.toJSON());
|
||||
const metadata = new Metadata(rpcData);
|
||||
const str = JSON.stringify(metadata.toJSON());
|
||||
|
||||
console.error(str);
|
||||
console.error(decoded.getUniqTypes());
|
||||
console.error(metadata.getUniqTypes());
|
||||
|
||||
expect(decoded.events.length).not.toBe(0);
|
||||
expect(metadata.events.length).not.toBe(0);
|
||||
expect(str).toEqual(JSON.stringify(latestParsed));
|
||||
});
|
||||
|
||||
describe('storage with default values', () => {
|
||||
const metadata = new Metadata(rpcData);
|
||||
|
||||
Method.injectMethods(extrinsicsFromMeta(metadata));
|
||||
|
||||
metadata.modules.forEach((mod) => {
|
||||
if (mod.storage.isNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
mod.storage.unwrap().functions.forEach((fn) => {
|
||||
it(`creates default types for ${mod.prefix}.${fn.name}, type ${fn.type}`, () => {
|
||||
expect(
|
||||
() => createType(fn.type.toString(), fn.default)
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 { AnyNumber, Codec } from '../types';
|
||||
import { AnyNumber } from '../types';
|
||||
|
||||
import Bytes from '../Bytes';
|
||||
import Enum from '../codec/Enum';
|
||||
@@ -13,7 +13,6 @@ import Vector from '../codec/Vector';
|
||||
import Text from '../Text';
|
||||
import Type from '../Type';
|
||||
import U16 from '../U16';
|
||||
// import { getTypeDef, getTypeClass } from '../codec/createType';
|
||||
|
||||
export class FunctionArgumentMetadata extends Struct {
|
||||
constructor (value?: any) {
|
||||
@@ -195,19 +194,12 @@ type StorageFunctionMetadataValue = {
|
||||
name: string | Text,
|
||||
modifier: StorageFunctionModifier | AnyNumber,
|
||||
type: StorageFunctionType,
|
||||
default: Codec, // More precisely, it's a `type`
|
||||
default: Bytes,
|
||||
documentation: Vector<Text> | Array<string>
|
||||
};
|
||||
|
||||
export class StorageFunctionMetadata<T extends Codec = Codec> extends Struct {
|
||||
// private _originalLength: number;
|
||||
|
||||
export class StorageFunctionMetadata extends Struct {
|
||||
constructor (value?: StorageFunctionMetadataValue | Uint8Array) {
|
||||
// We try to figure out the type of the storage function (default to Bytes)
|
||||
// const Type = value && (value as StorageFunctionMetadataValue).type
|
||||
// ? getTypeClass(getTypeDef((value as StorageFunctionMetadataValue).type.toString())) as Constructor<T>
|
||||
// : Bytes;
|
||||
|
||||
super({
|
||||
name: Text,
|
||||
modifier: StorageFunctionModifier,
|
||||
@@ -215,36 +207,13 @@ export class StorageFunctionMetadata<T extends Codec = Codec> extends Struct {
|
||||
default: Bytes,
|
||||
documentation: Vector.with(Text)
|
||||
}, value);
|
||||
|
||||
// this._originalLength = super.encodedLength;
|
||||
|
||||
// If, after construction, we "learned" (i.e. decoded) the type, then we
|
||||
// decode the `default` Bytes with the new type.
|
||||
// FIXME But for now, we don't do this, just leave the hex value as-is.
|
||||
// if (
|
||||
// this.get('default') instanceof Bytes &&
|
||||
// this.get('type')!.toString() !== 'Bytes'
|
||||
// ) {
|
||||
// const NewType = getTypeClass(getTypeDef(this.get('type')!.toString()));
|
||||
// this.set('default', new NewType(this.get('default')) as T);
|
||||
// }
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @description The length of the value when encoded as a Uint8Array
|
||||
// */
|
||||
// get encodedLength (): number {
|
||||
// // NOTE Length is used in the decoding calculations, so return the original (pre-cleanup)
|
||||
// // length of the data. Since toU8a is disabled, this does not affect encoding, but rather
|
||||
// // only the decoding leg, allowing the decoders to work with original pointers
|
||||
// return this._originalLength;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @description The default value of the storage function
|
||||
*/
|
||||
get default (): T {
|
||||
return this.get('default') as T;
|
||||
get default (): Bytes {
|
||||
return this.get('default') as Bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,12 +11,6 @@ describe('Method', () => {
|
||||
Method.injectMethods(extrinsics);
|
||||
});
|
||||
|
||||
it('throws when not decodable', () => {
|
||||
expect(
|
||||
() => new Method('foo')
|
||||
).toThrowError(/Method: cannot decode value/);
|
||||
});
|
||||
|
||||
it('handles decoding correctly (bare)', () => {
|
||||
expect(
|
||||
new Method({
|
||||
|
||||
@@ -126,7 +126,14 @@ export default class Method extends Struct {
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Method: cannot decode value '${value}' or type ${typeof value}`);
|
||||
console.error(`Method: cannot decode value '${value}' of type ${typeof value}`);
|
||||
|
||||
return {
|
||||
args: new Uint8Array(),
|
||||
argsDef: {},
|
||||
meta: new FunctionMetadata(),
|
||||
callIndex: new Uint8Array([255, 255])
|
||||
};
|
||||
}
|
||||
|
||||
// If the extrinsic function has an argument of type `Origin`, we ignore it
|
||||
|
||||
@@ -24,11 +24,13 @@ export interface StorageFunction {
|
||||
* constructed by passing in a raw key or a StorageFunction with (optional) arguments.
|
||||
*/
|
||||
export default class StorageKey extends Bytes {
|
||||
private _meta: StorageFunctionMetadata | null;
|
||||
private _outputType: string | null;
|
||||
|
||||
constructor (value: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]) {
|
||||
super(StorageKey.decodeStorageKey(value));
|
||||
|
||||
this._meta = StorageKey.getMeta(value as StorageKey);
|
||||
this._outputType = StorageKey.getType(value as StorageKey);
|
||||
}
|
||||
|
||||
@@ -46,6 +48,20 @@ export default class StorageKey extends Bytes {
|
||||
return value as Uint8Array;
|
||||
}
|
||||
|
||||
static getMeta (value: StorageKey | StorageFunction | [StorageFunction, any]): StorageFunctionMetadata | null {
|
||||
if (value instanceof StorageKey) {
|
||||
return value.meta;
|
||||
} else if (isFunction(value)) {
|
||||
return value.meta;
|
||||
} else if (Array.isArray(value)) {
|
||||
const [fn] = value;
|
||||
|
||||
return fn.meta;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static getType (value: StorageKey | StorageFunction | [StorageFunction, any]): string | null {
|
||||
if (value instanceof StorageKey) {
|
||||
return value.outputType;
|
||||
@@ -60,6 +76,13 @@ export default class StorageKey extends Bytes {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The metadata or `null` when not available
|
||||
*/
|
||||
get meta (): StorageFunctionMetadata | null {
|
||||
return this._meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The output type, `null` when not available
|
||||
*/
|
||||
|
||||
@@ -41,8 +41,14 @@ export default abstract class AbstractInt extends BN implements Codec {
|
||||
if (isHex(value)) {
|
||||
return hexToBn(value, { isLe: true, isNegative }).toString();
|
||||
} else if (isU8a(value)) {
|
||||
// NOTE When passing u8a in (typically from decoded data), it is always LE
|
||||
return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString();
|
||||
try {
|
||||
// NOTE When passing u8a in (typically from decoded data), it is always LE
|
||||
return u8aToBn(value.subarray(0, bitLength / 8), { isLe: true, isNegative }).toString();
|
||||
} catch (error) {
|
||||
console.error(`AbstractInt value decoding failed ${error.message}`, value);
|
||||
|
||||
return '0';
|
||||
}
|
||||
} else if (isString(value)) {
|
||||
return new BN(value, 10).toString();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export default class EnumType<T> extends Base<Codec> implements Codec {
|
||||
return EnumType.createValue(def, index, value);
|
||||
}
|
||||
|
||||
private static createValue (def: TypesDef, index: number, value?: any): Decoded {
|
||||
private static createValue (def: TypesDef, index: number = 0, value?: any): Decoded {
|
||||
return {
|
||||
index,
|
||||
value: new (Object.values(def)[index])(value)
|
||||
|
||||
@@ -16,13 +16,13 @@ import { AnyU8a, Codec } from '../types';
|
||||
* @noInheritDoc
|
||||
*/
|
||||
export default class U8a extends Uint8Array implements Codec {
|
||||
constructor (value: AnyU8a) {
|
||||
constructor (value?: AnyU8a) {
|
||||
super(
|
||||
U8a.decodeU8a(value)
|
||||
);
|
||||
}
|
||||
|
||||
private static decodeU8a (value: any): Uint8Array {
|
||||
private static decodeU8a (value?: any): Uint8Array {
|
||||
if (isU8a(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2019 @polkadot/types 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 { Constructor } from './types';
|
||||
|
||||
import extrinsics from '@polkadot/extrinsics/static';
|
||||
|
||||
import * as _Types from './index';
|
||||
|
||||
const Types = _Types as { [index: string]: Constructor };
|
||||
|
||||
describe('types', () => {
|
||||
describe('default creation', () => {
|
||||
Object.keys(Types).forEach((name) => {
|
||||
it(`creates an empty ${name}`, () => {
|
||||
const constructFn = () =>
|
||||
new Types[name]();
|
||||
|
||||
if (name === 'Origin') {
|
||||
expect(constructFn).toThrow();
|
||||
} else {
|
||||
expect(constructFn).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('default creation (empty Bytes)', () => {
|
||||
(Types.Method as any).injectMethods(extrinsics);
|
||||
|
||||
Object.keys(Types).forEach((name) => {
|
||||
it(`creates an empty ${name} (from empty bytes)`, () => {
|
||||
const constructFn = () =>
|
||||
new Types[name](new Types.Bytes());
|
||||
|
||||
if (name === 'Origin') {
|
||||
expect(constructFn).toThrow();
|
||||
} else {
|
||||
expect(constructFn).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user