Instantiate EnumType via Object (not Array) (#469)
* Instantiate EnumType via Object (not Array) * Split decoding into steps... slight simplification * Use assert for index check * Add JSON decoding tests
This commit is contained in:
@@ -72,12 +72,12 @@ export class Seal extends Tuple {
|
||||
export class DigestItem extends EnumType<AuthoritiesChange | ChangesTrieRoot | Other
|
||||
| Seal> {
|
||||
constructor (value: any) {
|
||||
super([
|
||||
super({
|
||||
Other, // Position 0, as per Rust (encoding control)
|
||||
AuthoritiesChange,
|
||||
ChangesTrieRoot,
|
||||
Seal
|
||||
], value);
|
||||
}, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ export class Finalization extends Null {
|
||||
*/
|
||||
export class Phase extends EnumType<ApplyExtrinsic | Finalization> {
|
||||
constructor (value: any, index?: number) {
|
||||
super([
|
||||
super({
|
||||
ApplyExtrinsic,
|
||||
Finalization
|
||||
], value, index);
|
||||
}, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,11 @@ export class Usurped extends Hash {
|
||||
*/
|
||||
export default class ExtrinsicStatus extends EnumType<Finalised | Usurped | Broadcast | Dropped> {
|
||||
constructor (value: any, index?: number) {
|
||||
super([
|
||||
super({
|
||||
Finalised,
|
||||
Usurped,
|
||||
Broadcast,
|
||||
Dropped
|
||||
], value, index);
|
||||
}, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,10 +152,10 @@ export class StorageFunctionType$Map extends Struct {
|
||||
|
||||
export class StorageFunctionType extends EnumType<Type | StorageFunctionType$Map> {
|
||||
constructor (value?: any, index?: number) {
|
||||
super([
|
||||
super({
|
||||
Type,
|
||||
StorageFunctionType$Map
|
||||
], value, index);
|
||||
}, value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,12 +120,12 @@ export class BftDoubleCommit extends BftAtReport {
|
||||
*/
|
||||
export class MisbehaviorKind extends EnumType<BftProposeOutOfTurn | BftDoublePropose | BftDoublePrepare | BftDoubleCommit> {
|
||||
constructor (value?: BftAtReportValue | Uint8Array, index?: number) {
|
||||
super([
|
||||
super({
|
||||
BftProposeOutOfTurn,
|
||||
BftDoublePropose,
|
||||
BftDoublePrepare,
|
||||
BftDoubleCommit
|
||||
], value, index);
|
||||
}, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-6
@@ -7,25 +7,46 @@ import Text from '../Text';
|
||||
import U32 from '../U32';
|
||||
|
||||
describe('Struct', () => {
|
||||
it('provides a clean toString() (raw)', () => {
|
||||
it('provides a clean toString() (value)', () => {
|
||||
expect(
|
||||
new EnumType(
|
||||
[Text, U32],
|
||||
{ Text, U32 },
|
||||
new Uint8Array([0, 2 << 2, 49, 50])
|
||||
).raw.toString()
|
||||
).value.toString()
|
||||
).toEqual('12');
|
||||
});
|
||||
|
||||
it('provides a clean toString() (enum)', () => {
|
||||
expect(
|
||||
new EnumType(
|
||||
[Text, U32],
|
||||
{ Text, U32 },
|
||||
new Uint8Array([1, 2 << 2, 49, 50])
|
||||
).toString()
|
||||
).toEqual('U32');
|
||||
});
|
||||
|
||||
it('allows checking against defined indexes', () => {
|
||||
it('decodes from a JSON input (lowercase)', () => {
|
||||
expect(
|
||||
new EnumType(
|
||||
{ Text, U32 },
|
||||
{ 'text': 'some text value' }
|
||||
).value.toString()
|
||||
).toEqual('some text value');
|
||||
});
|
||||
|
||||
it('decodes from a JSON input (mixed case)', () => {
|
||||
expect(
|
||||
new EnumType(
|
||||
{ Text, U32 },
|
||||
{ 'U32': 42 }
|
||||
).value.toString()
|
||||
).toEqual('42');
|
||||
});
|
||||
|
||||
// We are currently not using this approach, none of the types in Substrate currently
|
||||
// have any overrides. Insteda of trying to support it (just-in-case), rather have it
|
||||
// removed to simplyfy the code - it can be pulled-back if needed
|
||||
it.skip('allows checking against defined indexes', () => {
|
||||
expect(
|
||||
new EnumType(
|
||||
{ 1: Text, 5: U32 },
|
||||
@@ -37,7 +58,7 @@ describe('Struct', () => {
|
||||
it('allows accessing the type and value', () => {
|
||||
const text = new Text('foo');
|
||||
const enumType = new EnumType(
|
||||
[Text, U32],
|
||||
{ Text, U32 },
|
||||
{ Text: text }
|
||||
);
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { isNumber, isObject, isU8a, isUndefined, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
import { assert, isNumber, isObject, isU8a, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import Base from './Base';
|
||||
import Null from '../Null';
|
||||
import { Codec, Constructor } from '../types';
|
||||
|
||||
type TypesArray = Array<Constructor>;
|
||||
type TypesDef = {
|
||||
[index: number]: Constructor
|
||||
} | TypesArray;
|
||||
[name: string]: Constructor
|
||||
};
|
||||
|
||||
type Decoded = {
|
||||
index: number,
|
||||
value: Codec
|
||||
};
|
||||
|
||||
/**
|
||||
* @name EnumType
|
||||
@@ -23,62 +27,61 @@ type TypesDef = {
|
||||
// - As per Enum, actually use TS enum
|
||||
// - It should rather probably extend Enum instead of copying code
|
||||
export default class EnumType<T> extends Base<Codec> implements Codec {
|
||||
private _Types: TypesArray;
|
||||
private _def: TypesDef;
|
||||
private _index: number;
|
||||
private _indexes: Array<number>;
|
||||
|
||||
constructor (def: TypesDef, value?: any, index?: number | EnumType<T>) {
|
||||
const decoded = EnumType.decodeEnumType(def, value, index);
|
||||
|
||||
super(
|
||||
decoded.value
|
||||
);
|
||||
|
||||
this._Types = Array.isArray(def)
|
||||
? def
|
||||
: Object.values(def);
|
||||
this._indexes = Object.keys(def).map((index) =>
|
||||
parseInt(index, 10)
|
||||
);
|
||||
super(decoded.value);
|
||||
|
||||
this._def = def;
|
||||
this._indexes = Object.keys(def).map((_, index) => index);
|
||||
this._index = this._indexes.indexOf(decoded.index) || 0;
|
||||
}
|
||||
|
||||
static decodeEnumType<T> (def: TypesDef, value?: any, index?: number | EnumType<T>): { index: number, value: any } {
|
||||
private static decodeEnumType<T> (def: TypesDef, value?: any, index?: number | EnumType<T>): Decoded {
|
||||
// If `index` is set, we parse it.
|
||||
if (index instanceof EnumType) {
|
||||
return { index: index._index, value: new def[index._index](index.raw) };
|
||||
return EnumType.createValue(def, index._index, index.raw);
|
||||
} else if (isNumber(index)) {
|
||||
return { index, value: new def[index](value) };
|
||||
return EnumType.createValue(def, index, value);
|
||||
}
|
||||
|
||||
// Or else, we just look at `value`
|
||||
if (isU8a(value)) {
|
||||
return { index: value[0], value: new def[value[0]](value.subarray(1)) };
|
||||
} else if (isNumber(value) && !isUndefined(def[value])) {
|
||||
return { index: value, value: new def[value]() };
|
||||
return EnumType.decodeViaValue(def, value);
|
||||
}
|
||||
|
||||
private static decodeViaValue (def: TypesDef, value?: any): Decoded {
|
||||
if (value instanceof EnumType) {
|
||||
return EnumType.createValue(def, value._index, value.raw);
|
||||
} else if (isU8a(value)) {
|
||||
return EnumType.createValue(def, value[0], value.subarray(1));
|
||||
} else if (isNumber(value)) {
|
||||
return EnumType.createValue(def, value);
|
||||
} else if (isObject(value)) {
|
||||
// JSON comes in the form of { "<type (lowercased)>": "<value for type>" }, here we
|
||||
// additionally force to lower to ensure forward compat
|
||||
const key = Object.keys(value)[0];
|
||||
const lowerKey = key.toLowerCase();
|
||||
const index = Object
|
||||
.keys(def)
|
||||
.find((k) => def[+k].name.toLowerCase() === lowerKey);
|
||||
const keys = Object.keys(def).map((k) => k.toLowerCase());
|
||||
const index = keys.indexOf(lowerKey);
|
||||
|
||||
if (isUndefined(index)) {
|
||||
const message = 'Unable to reliably map input on JSON';
|
||||
assert(index !== -1, 'Unable to reliably map input on JSON');
|
||||
|
||||
console.error(message, value, def);
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return { index: +index, value: new def[+index](value[key]) };
|
||||
return EnumType.createValue(def, index, value[key]);
|
||||
}
|
||||
|
||||
// Worst-case scenario, return this
|
||||
return { index: 0, value: new (Object.values(def)[0])() };
|
||||
// Worst-case scenario, return the first with default
|
||||
return EnumType.createValue(def, 0);
|
||||
}
|
||||
|
||||
private static createValue (def: TypesDef, index: number, value?: any): Decoded {
|
||||
return {
|
||||
index,
|
||||
value: new (Object.values(def)[index])(value)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +109,7 @@ export default class EnumType<T> extends Base<Codec> implements Codec {
|
||||
* @description The name of the type this enum value represents
|
||||
*/
|
||||
get type (): string {
|
||||
return this._Types[this._index].name;
|
||||
return Object.keys(this._def)[this._index];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user