Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow string types inputs into codec #1292

Merged
merged 5 commits into from
Aug 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/types/src/codec/Compact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ describe('Compact', (): void => {
).toEqual(64);
});

it('has the correct encodedLength for constructor values (BlockNumber)', (): void => {
it('has the correct encodedLength for constructor values (string BlockNumber)', (): void => {
expect(
new Compact('BlockNumber', 0xfffffff9).encodedLength
).toEqual(5);
});

it('has the correct encodedLength for constructor values (class BlockNumber)', (): void => {
expect(
new Compact(ClassOf('BlockNumber'), 0xfffffff9).encodedLength
).toEqual(5);
Expand Down
9 changes: 5 additions & 4 deletions packages/types/src/codec/Compact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { AnyNumber, Constructor } from '../types';
import { AnyNumber, Constructor, InterfaceTypes } from '../types';

import BN from 'bn.js';
import { bnToBn, compactAddLength, compactFromU8a, compactStripLength, compactToU8a, hexToBn, isBn, isHex, isNumber, isString } from '@polkadot/util';
import { DEFAULT_BITLENGTH } from '@polkadot/util/compact/defaults';

import { typeToConstructor } from './utils';
import { UIntBitLength } from './AbstractInt';
import CodecDate from './Date';
import UInt from './UInt';
Expand All @@ -26,11 +27,11 @@ export type CompactEncodable = UInt | CodecDate; // FIXME is there a way to do i
* a number and making the compact representation thereof
*/
export default class Compact<T extends CompactEncodable> extends Base<T> {
public constructor (Type: Constructor<T>, value: Compact<T> | AnyNumber = 0) {
super(Compact.decodeCompact<T>(Type, value));
public constructor (Type: Constructor<T> | InterfaceTypes, value: Compact<T> | AnyNumber = 0) {
super(Compact.decodeCompact<T>(typeToConstructor(Type), value));
}

public static with<T extends CompactEncodable> (Type: Constructor<T>): Constructor<Compact<T>> {
public static with<T extends CompactEncodable> (Type: Constructor<T> | InterfaceTypes): Constructor<Compact<T>> {
return class extends Compact<T> {
public constructor (value?: any) {
super(Type, value);
Expand Down
9 changes: 9 additions & 0 deletions packages/types/src/codec/EnumType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ describe('Enum', (): void => {
).toEqual('4660'); // 0x1234 in decimal
});

it('decodes from hex (string types)', (): void => {
expect(
new Enum(
{ foo: 'Text', bar: 'u32' },
'0x0134120000'
).value.toString()
).toEqual('4660'); // 0x1234 in decimal
});

it('decodes from a JSON input (mixed case)', (): void => {
expect(
new Enum(
Expand Down
11 changes: 6 additions & 5 deletions packages/types/src/codec/EnumType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { AnyJson, Codec, Constructor } from '../types';
import { AnyJson, Codec, Constructor, InterfaceTypes } from '../types';

import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, stringCamelCase, stringUpperFirst, u8aConcat, u8aToHex } from '@polkadot/util';

import Null from '../primitive/Null';
import { mapToTypeMap } from './utils';
import Base from './Base';

interface EnumConstructor<T = Codec> {
Expand Down Expand Up @@ -38,7 +39,7 @@ export default class Enum extends Base<Codec> {

private _isBasic: boolean;

public constructor (def: TypesDef | string[], value?: any, index?: number | Enum) {
public constructor (def: Record<string, InterfaceTypes | Constructor> | string[], value?: any, index?: number | Enum) {
const defInfo = Enum.extractDef(def);
const decoded = Enum.decodeEnum(defInfo.def, value, index);

Expand All @@ -50,10 +51,10 @@ export default class Enum extends Base<Codec> {
this._index = this._indexes.indexOf(decoded.index) || 0;
}

private static extractDef (def: TypesDef | string[]): { def: TypesDef; isBasic: boolean } {
private static extractDef (def: Record<string, InterfaceTypes | Constructor> | string[]): { def: TypesDef; isBasic: boolean } {
if (!Array.isArray(def)) {
return {
def,
def: mapToTypeMap(def),
isBasic: false
};
}
Expand Down Expand Up @@ -126,7 +127,7 @@ export default class Enum extends Base<Codec> {
};
}

public static with (Types: TypesDef | string[]): EnumConstructor<Enum> {
public static with (Types: Record<string, InterfaceTypes | Constructor> | string[]): EnumConstructor<Enum> {
return class extends Enum {
public constructor (value?: any, index?: number) {
super(Types, value, index);
Expand Down
20 changes: 16 additions & 4 deletions packages/types/src/codec/Linkage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { Struct, Option, Tuple, Vec } from '.';
import { Constructor, Codec } from '../types';
import { Constructor, Codec, InterfaceTypes } from '../types';

import Option from './Option';
import Struct from './Struct';
import Tuple from './Tuple';
import Vec from './Vec';

type TypeWithValues = [Constructor, any[]];

const EMPTY = new Uint8Array();

/**
* @name Linkage
* @description The wrapper for the result from a LinkedMap
*/
export default class Linkage<T extends Codec> extends Struct {
public constructor (Type: Constructor, value?: any) {
public constructor (Type: Constructor | InterfaceTypes, value?: any) {
super({
previous: Option.with(Type),
next: Option.with(Type)
}, value);
}

public static withKey<O extends Codec> (Type: Constructor): Constructor<Linkage<O>> {
public static withKey<O extends Codec> (Type: Constructor | InterfaceTypes): Constructor<Linkage<O>> {
return class extends Linkage<O> {
public constructor (value?: any) {
super(Type, value);
Expand Down Expand Up @@ -50,6 +58,10 @@ export default class Linkage<T extends Codec> extends Struct {
}
}

/**
* @name LinkageResult
* @description A Linkage keys/Values tuple
*/
export class LinkageResult extends Tuple {
public constructor ([TypeKey, keys]: TypeWithValues, [TypeValue, values]: TypeWithValues) {
super({
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/codec/Option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ describe('Option', (): void => {
).toEqual('hello');
});

it('converts an option to an option (strings)', (): void => {
expect(
new Option('Text', new Option('Text', 'hello')).toString()
).toEqual('hello');
});

it('converts correctly from hex with toHex (Bytes)', (): void => {
// Option<Bytes> for a parachain head, however, this is effectively an
// Option<Option<Bytes>> (hence the length, since it is from storage)
Expand Down
18 changes: 10 additions & 8 deletions packages/types/src/codec/Option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// 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, InterfaceTypes } from '../types';

import { isNull, isU8a, isUndefined, u8aToHex } from '@polkadot/util';

import Base from './Base';
import { Codec, Constructor } from '../types';
import Null from '../primitive/Null';
import { typeToConstructor } from './utils';
import Base from './Base';

/**
* @name Option
Expand All @@ -19,12 +21,12 @@ import Null from '../primitive/Null';
export default class Option<T extends Codec> extends Base<T> {
private _Type: Constructor;

public constructor (Type: Constructor, value?: any) {
super(
Option.decodeOption(Type, value)
);
public constructor (Type: Constructor | InterfaceTypes, value?: any) {
const Clazz = typeToConstructor(Type);

super(Option.decodeOption(Clazz, value));

this._Type = Type;
this._Type = Clazz;
}

public static decodeOption (Type: Constructor, value?: any): Codec {
Expand All @@ -46,7 +48,7 @@ export default class Option<T extends Codec> extends Base<T> {
return new Type(value);
}

public static with<O extends Codec> (Type: Constructor): Constructor<Option<O>> {
public static with<O extends Codec> (Type: Constructor | InterfaceTypes): Constructor<Option<O>> {
return class extends Option<O> {
public constructor (value?: any) {
super(Type, value);
Expand Down
12 changes: 12 additions & 0 deletions packages/types/src/codec/Struct.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ describe('Struct', (): void => {
).toEqual('{"txt":"foo","u32":1193046}');
});

it('provides a clean toString() (string types)', (): void => {
expect(
new (
Struct.with({
txt: 'Text',
num: 'u32',
cls: U32
})
)({ txt: 'foo', num: 0x123456, cls: 123 }).toString()
).toEqual('{"txt":"foo","num":1193046,"cls":123}');
});

it('exposes the properties on the object', (): void => {
const struct = new (
Struct.with({
Expand Down
45 changes: 18 additions & 27 deletions packages/types/src/codec/Struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { AnyJsonObject, Codec, Constructor, ConstructorDef, IHash } from '../types';
import { AnyJsonObject, Codec, Constructor, ConstructorDef, IHash, InterfaceTypes } from '../types';

import { hexToU8a, isHex, isObject, isU8a, isUndefined, u8aConcat, u8aToHex } from '@polkadot/util';
import { blake2AsU8a } from '@polkadot/util-crypto';

import U8a from './U8a';
import { compareMap, decodeU8a } from './utils';
import { compareMap, decodeU8a, mapToTypeMap } from './utils';

type TypesDef<T = Codec> = Record<string, InterfaceTypes | Constructor<T>>;

/**
* @name Struct
Expand All @@ -22,7 +24,7 @@ import { compareMap, decodeU8a } from './utils';
*/
export default class Struct<
// The actual Class structure, i.e. key -> Class
S extends ConstructorDef = ConstructorDef,
S extends TypesDef = TypesDef,
// internal type, instance of classes mapped by key
T extends { [K in keyof S]: Codec } = { [K in keyof S]: Codec },
// input values, mapped by key can be anything (construction)
Expand All @@ -31,17 +33,16 @@ export default class Struct<
E extends { [K in keyof S]: string } = { [K in keyof S]: string }> extends Map<keyof S, Codec> implements Codec {
protected _jsonMap: Map<keyof S, string>;

protected _Types: S;
protected _Types: ConstructorDef;

public constructor (Types: S, value: V | Map<any, any> | any[] | string = {} as unknown as V, jsonMap: Map<keyof S, string> = new Map()) {
const decoded = Struct.decodeStruct<S, V, T>(Types, value, jsonMap);
const Clazzes = mapToTypeMap(Types);
const decoded = Struct.decodeStruct(Clazzes, value, jsonMap);

super(
Object.entries(decoded)
);
super(Object.entries(decoded as any));

this._jsonMap = jsonMap;
this._Types = Types;
this._Types = Clazzes;
}

/**
Expand All @@ -59,11 +60,7 @@ export default class Struct<
* `Object.keys(Types)`
* @param jsonMap
*/
private static decodeStruct<
S extends ConstructorDef,
_,
T extends { [K in keyof S]: Codec }
> (Types: S, value: any, jsonMap: Map<keyof S, string>): T {
private static decodeStruct <T> (Types: ConstructorDef, value: any, jsonMap: Map<any, string>): T {
// l.debug(() => ['Struct.decode', { Types, value }]);

if (isHex(value)) {
Expand All @@ -72,7 +69,7 @@ export default class Struct<
const values = decodeU8a(value, Object.values(Types));

// Transform array of values to {key: value} mapping
return Object.keys(Types).reduce((raw: T, key: keyof S, index): T => {
return Object.keys(Types).reduce((raw, key, index): T => {
// TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'.
(raw as any)[key] = values[index];

Expand All @@ -86,30 +83,26 @@ export default class Struct<
return Struct.decodeStructFromObject(Types, value, jsonMap);
}

private static decodeStructFromObject<
S extends ConstructorDef,
_,
T extends { [K in keyof S]: Codec }
> (Types: S, value: any, jsonMap: Map<keyof S, string>): T {
return Object.keys(Types).reduce((raw: T, key: keyof S, index): T => {
private static decodeStructFromObject <T> (Types: ConstructorDef, value: any, jsonMap: Map<any, string>): T {
return Object.keys(Types).reduce((raw, key, index): T => {
// The key in the JSON can be snake_case (or other cases), but in our
// Types, result or any other maps, it's camelCase
const jsonKey = (jsonMap.get(key as any) && !value[key]) ? jsonMap.get(key as any) : key;

try {
if (Array.isArray(value)) {
raw[key] = value[index] instanceof Types[key]
// TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'.
(raw as any)[key] = value[index] instanceof Types[key]
? value[index]
: new Types[key](value[index]);
} else if (value instanceof Map) {
const mapped = value.get(jsonKey);

// TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'.
(raw as any)[key] = mapped instanceof Types[key]
? mapped
: new Types[key](mapped);
} else if (isObject(value)) {
raw[key] = value[jsonKey as string] instanceof Types[key]
(raw as any)[key] = value[jsonKey as string] instanceof Types[key]
? value[jsonKey as string]
: new Types[key](value[jsonKey as string]);
} else {
Expand All @@ -123,9 +116,7 @@ export default class Struct<
}, {} as unknown as T);
}

public static with<
S extends ConstructorDef
> (Types: S): Constructor<Struct<S>> {
public static with<S extends TypesDef> (Types: S): Constructor<Struct<S>> {
return class extends Struct<S> {
public constructor (value?: any, jsonMap?: Map<keyof S, string>) {
super(Types, value, jsonMap);
Expand Down
9 changes: 9 additions & 0 deletions packages/types/src/codec/Tuple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ describe('Tuple', (): void => {
testEncode('toString', '["bazzing",69]');
});

it('creates from string types', (): void => {
expect(
new Tuple(
['Text', 'u32', U32],
['foo', 69, 42]
).toString()
).toEqual('["foo",69,42]');
});

it.skip('creates properly via actual hex string', (): void => {
Call.injectMethods(extrinsics);

Expand Down