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:
Jaco Greeff
2018-12-14 15:09:21 +01:00
committed by GitHub
parent a8d108d221
commit 4291a49d22
9 changed files with 81 additions and 57 deletions
+2 -2
View File
@@ -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);
}
}
+2 -2
View File
@@ -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);
}
}
+2 -2
View File
@@ -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);
}
}
+2 -2
View File
@@ -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);
}
/**
+2 -2
View File
@@ -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);
}
}
@@ -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 }
);
+39 -36
View File
@@ -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];
}
/**