Text extends String (#399)

* Text extends String

* Fix type

* Cleaner code

* Add explicit return type
This commit is contained in:
Amaury Martiny
2018-11-15 14:19:41 +01:00
committed by GitHub
parent ca2f69d086
commit f381a4d045
3 changed files with 57 additions and 54 deletions
+3 -2
View File
@@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details. // of the ISC license. See the LICENSE file for details.
import { CodecTo } from './types';
import Text from './Text'; import Text from './Text';
import U8a from './codec/U8a'; import U8a from './codec/U8a';
@@ -20,12 +21,12 @@ describe('Text', () => {
}); });
describe('encode', () => { describe('encode', () => {
const testEncode = (to: 'toString' | 'toU8a', expected: string | Uint8Array) => const testEncode = (to: CodecTo, expected: string | Uint8Array) =>
it(`can encode ${to}`, () => { it(`can encode ${to}`, () => {
expect(new Text('foo')[to]()).toEqual(expected); expect(new Text('foo')[to]()).toEqual(expected);
}); });
// testEncode('toHex', '0x0c666f6f'); // FIXME Add this testEncode('toHex', '0x0c666f6f');
testEncode('toString', 'foo'); testEncode('toString', 'foo');
testEncode('toU8a', Uint8Array.from([12, 102, 111, 111])); testEncode('toU8a', Uint8Array.from([12, 102, 111, 111]));
}); });
+13 -17
View File
@@ -2,10 +2,9 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details. // of the ISC license. See the LICENSE file for details.
import { isFunction, isString, stringToU8a, u8aToString, u8aConcat } from '@polkadot/util'; import { isFunction, isString, stringToU8a, u8aToString, u8aConcat, u8aToHex } from '@polkadot/util';
import { AnyU8a } from '@polkadot/types/types'; import { AnyU8a, Codec } from './types';
import Base from './codec/Base';
import Compact, { DEFAULT_LENGTH_BITS } from './codec/Compact'; import Compact, { DEFAULT_LENGTH_BITS } from './codec/Compact';
// This is a string wrapper, along with the length. It is used both for strings as well // This is a string wrapper, along with the length. It is used both for strings as well
@@ -16,18 +15,19 @@ import Compact, { DEFAULT_LENGTH_BITS } from './codec/Compact';
// - Potentially we want a "TypeString" extension to this. Basically something that // - Potentially we want a "TypeString" extension to this. Basically something that
// wraps the `Balance`, `T::AccountId`, etc. The reasoning - with a "TypeString" // wraps the `Balance`, `T::AccountId`, etc. The reasoning - with a "TypeString"
// we can nicely strip types down like "T::AcountId" -> "AccountId" // we can nicely strip types down like "T::AcountId" -> "AccountId"
export default class Text extends Base<string> { export default class Text extends String implements Codec {
public raw: String; // FIXME Remove this once we convert all types out of Base
constructor (value: Text | string | AnyU8a | { toString: () => string } = '') { constructor (value: Text | string | AnyU8a | { toString: () => string } = '') {
super( super(
Text.decodeText(value) Text.decodeText(value)
); );
this.raw = this;
} }
static decodeText (value: Text | string | AnyU8a | { toString: () => string }): string { private static decodeText (value: Text | string | AnyU8a | { toString: () => string }): string {
if (isString(value)) { if (isString(value)) {
return value; return value.toString();
} else if (value instanceof Text) {
return value.raw;
} else if (value instanceof Uint8Array) { } else if (value instanceof Uint8Array) {
const [offset, length] = Compact.decodeU8a(value, DEFAULT_LENGTH_BITS); const [offset, length] = Compact.decodeU8a(value, DEFAULT_LENGTH_BITS);
@@ -39,24 +39,20 @@ export default class Text extends Base<string> {
return `${value}`; return `${value}`;
} }
get length (): number {
return this.raw.length;
}
get encodedLength (): number { get encodedLength (): number {
return this.length + Compact.encodeU8a(this.length, DEFAULT_LENGTH_BITS).length; return this.length + Compact.encodeU8a(this.length, DEFAULT_LENGTH_BITS).length;
} }
toHex (): string {
return u8aToHex(this.toU8a());
}
toJSON (): any { toJSON (): any {
return this.toString(); return this.toString();
} }
toString (): string {
return this.raw;
}
toU8a (isBare?: boolean): Uint8Array { toU8a (isBare?: boolean): Uint8Array {
const encoded = stringToU8a(this.raw); const encoded = stringToU8a(this.toString());
return isBare return isBare
? encoded ? encoded
+41 -35
View File
@@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms // This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details. // of the ISC license. See the LICENSE file for details.
import Compact, { DEFAULT_LENGTH_BITS } from './codec/Compact';
import U8a from './codec/U8a'; import U8a from './codec/U8a';
import Text from './Text'; import Text from './Text';
@@ -13,30 +14,24 @@ const ALLOWED_BOXES = ['Compact', 'Option', 'Vec'];
// what string provides us, however we also "tweak" the types received from the runtime, i.e. // what string provides us, however we also "tweak" the types received from the runtime, i.e.
// we remove the `T::` prefixes found in some types for consistency accross implementation. // we remove the `T::` prefixes found in some types for consistency accross implementation.
export default class Type extends Text { export default class Type extends Text {
private _originalLength: number = 0; private _originalLength: number;
constructor (value?: Text | U8a | Uint8Array | string) { constructor (value: Text | U8a | Uint8Array | string = '') {
super(value); // First decode it with Text
const textValue = new Text(value).toString();
// Then cleanup the textValue to get the @polkadot/types type, and pass the
// sanitized value to constructor
super(
Type.decodeType(
textValue
)
);
this._originalLength = textValue.length;
this._cleanupTypes();
} }
// NOTE Length is used in the decoding calculations, so return the original (pre-cleanup) private static decodeType (value: string): string {
// 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
get length (): number {
return this._originalLength;
}
// Note Since we are mangling what we get in beyond recognition, we really should
// not allow the re-encoding. Additionally, this is probably more of a decoder-only
// helper, so treat it as such.
toU8a (isBare?: boolean): Uint8Array {
throw new Error('Type::toU8a: unimplemented');
}
// HACK(ery) Take the types and tweak them (slightly?) for consistency
private _cleanupTypes (): Type {
const mappings: Array<Mapper> = [ const mappings: Array<Mapper> = [
// <T::Balance as HasCompact> // <T::Balance as HasCompact>
this._cleanupCompact(), this._cleanupCompact(),
@@ -58,16 +53,27 @@ export default class Type extends Text {
// `PropIndex` -> `ProposalIndex` (implementation looks the same, however meant as diff) // `PropIndex` -> `ProposalIndex` (implementation looks the same, however meant as diff)
]; ];
this._originalLength = this.raw.length; return mappings.reduce((result, fn) => {
this.raw = mappings.reduce((result, fn) => {
return fn(result); return fn(result);
}, this.raw).trim(); }, value).trim();
}
return this; // 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
get encodedLength (): number {
return this._originalLength + Compact.encodeU8a(this._originalLength, DEFAULT_LENGTH_BITS).length;
}
// Note Since we are mangling what we get in beyond recognition, we really should
// not allow the re-encoding. Additionally, this is probably more of a decoder-only
// helper, so treat it as such.
toU8a (isBare?: boolean): Uint8Array {
throw new Error('Type::toU8a: unimplemented');
} }
// given a starting index, find the closing > // given a starting index, find the closing >
private _findClosing (value: string, start: number): number { private static _findClosing (value: string, start: number): number {
let depth = 0; let depth = 0;
for (let index = start; index < value.length; index++) { for (let index = start; index < value.length; index++) {
@@ -85,7 +91,7 @@ export default class Type extends Text {
throw new Error(`Unable to find closing matching <> on '${value}' (start ${start})`); throw new Error(`Unable to find closing matching <> on '${value}' (start ${start})`);
} }
private _alias (src: string, dest: string): Mapper { private static _alias (src: string, dest: string): Mapper {
return (value: string): string => { return (value: string): string => {
return value.replace( return value.replace(
new RegExp(src, 'g'), dest new RegExp(src, 'g'), dest
@@ -93,14 +99,14 @@ export default class Type extends Text {
}; };
} }
private _cleanupCompact (): Mapper { private static _cleanupCompact (): Mapper {
return (value: string): string => { return (value: string): string => {
for (let index = 0; index < value.length; index++) { for (let index = 0; index < value.length; index++) {
if (value[index] !== '<') { if (value[index] !== '<') {
continue; continue;
} }
const end = this._findClosing(value, index + 1) - 14; const end = Type._findClosing(value, index + 1) - 14;
if (value.substr(end, 14) === ' as HasCompact') { if (value.substr(end, 14) === ' as HasCompact') {
value = `Compact<${value.substr(index + 1, end - index - 1)}>`; value = `Compact<${value.substr(index + 1, end - index - 1)}>`;
@@ -111,7 +117,7 @@ export default class Type extends Text {
}; };
} }
private _removeGenerics (): Mapper { private static _removeGenerics (): Mapper {
return (value: string): string => { return (value: string): string => {
for (let index = 0; index < value.length; index++) { for (let index = 0; index < value.length; index++) {
if (value[index] === '<') { if (value[index] === '<') {
@@ -124,7 +130,7 @@ export default class Type extends Text {
// we have not found anything, unwrap generic innards // we have not found anything, unwrap generic innards
if (!box) { if (!box) {
const end = this._findClosing(value, index + 1); const end = Type._findClosing(value, index + 1);
value = `${value.substr(0, index)}${value.substr(end + 1)}`; value = `${value.substr(0, index)}${value.substr(end + 1)}`;
} }
@@ -136,12 +142,12 @@ export default class Type extends Text {
} }
// remove the PairOf wrappers // remove the PairOf wrappers
private _removePairOf (): Mapper { private static _removePairOf (): Mapper {
return (value: string): string => { return (value: string): string => {
for (let index = 0; index < value.length; index++) { for (let index = 0; index < value.length; index++) {
if (value.substr(index, 7) === 'PairOf<') { if (value.substr(index, 7) === 'PairOf<') {
const start = index + 7; const start = index + 7;
const end = this._findClosing(value, start); const end = Type._findClosing(value, start);
const type = value.substr(start, end - start); const type = value.substr(start, end - start);
value = `${value.substr(0, index)}(${type}, ${type})${value.substr(end + 1)}`; value = `${value.substr(0, index)}(${type}, ${type})${value.substr(end + 1)}`;
@@ -153,7 +159,7 @@ export default class Type extends Text {
} }
// remove the type traits // remove the type traits
private _removeTraits (): Mapper { private static _removeTraits (): Mapper {
return (value: string): string => { return (value: string): string => {
return value return value
// anything `T::<type>` to end up as `<type>` // anything `T::<type>` to end up as `<type>`
@@ -168,7 +174,7 @@ export default class Type extends Text {
} }
// remove wrapping values, i.e. Box<Proposal> -> Proposal // remove wrapping values, i.e. Box<Proposal> -> Proposal
private _removeWrap (_check: string): Mapper { private static _removeWrap (_check: string): Mapper {
const check = `${_check}<`; const check = `${_check}<`;
return (value: string): string => { return (value: string): string => {
@@ -179,7 +185,7 @@ export default class Type extends Text {
if (index !== -1) { if (index !== -1) {
const start = index + check.length; const start = index + check.length;
const end = this._findClosing(value, start); const end = Type._findClosing(value, start);
value = `${value.substr(0, index)}${value.substr(start, end - start)}${value.substr(end + 1)}`; value = `${value.substr(0, index)}${value.substr(start, end - start)}${value.substr(end + 1)}`;
} }