UNPKG

@polkadot/types-codec

Version:
261 lines (260 loc) 8.9 kB
import { isBoolean, isHex, isObject, isU8a, isUndefined, objectProperties, stringCamelCase, stringify, u8aConcatStrict, u8aToHex, u8aToU8a } from '@polkadot/util'; import { compareMap, decodeU8aStruct, mapToTypeMap, typesToMap } from '../utils/index.js'; function noopSetDefinition(d) { return d; } /** @internal */ function decodeStructFromObject(registry, [Types, keys], value, jsonMap) { let jsonObj; const typeofArray = Array.isArray(value); const typeofMap = value instanceof Map; const count = keys.length; if (!typeofArray && !typeofMap && !isObject(value)) { throw new Error(`Struct: Cannot decode value ${stringify(value)} (typeof ${typeof value}), expected an input object, map or array`); } else if (typeofArray && value.length !== count) { throw new Error(`Struct: Unable to map ${stringify(value)} array to object with known keys ${keys.join(', ')}`); } const raw = new Array(count); for (let i = 0; i < count; i++) { const key = keys[i]; const jsonKey = jsonMap.get(key) || key; const Type = Types[i]; let assign; try { if (typeofArray) { assign = value[i]; } else if (typeofMap) { assign = jsonKey && value.get(jsonKey); } else { assign = jsonKey && Object.prototype.hasOwnProperty.call(value, jsonKey) ? value[jsonKey] : undefined; if (isUndefined(assign)) { if (isUndefined(jsonObj)) { const entries = Object.entries(value); jsonObj = {}; for (let e = 0, ecount = entries.length; e < ecount; e++) { if (Object.prototype.hasOwnProperty.call(value, entries[e][0])) { jsonObj[stringCamelCase(entries[e][0])] = entries[e][1]; } } } assign = jsonKey && Object.prototype.hasOwnProperty.call(jsonObj, jsonKey) ? jsonObj[jsonKey] : undefined; } } raw[i] = [ key, assign instanceof Type ? assign : new Type(registry, assign) ]; } catch (error) { let type = Type.name; try { type = new Type(registry).toRawType(); } catch { // ignore } throw new Error(`Struct: failed on ${jsonKey}: ${type}:: ${error.message}`); } } return [raw, 0]; } /** * @name Struct * @description * A Struct defines an Object with key-value pairs - where the values are Codec values. It removes * a lot of repetition from the actual coding, define a structure type, pass it the key/Codec * values in the constructor and it manages the decoding. It is important that the constructor * values matches 100% to the order in th Rust code, i.e. don't go crazy and make it alphabetical, * it needs to decoded in the specific defined order. * @noInheritDoc */ export class Struct extends Map { registry; createdAtHash; initialU8aLength; isStorageFallback; __internal__jsonMap; __internal__Types; constructor(registry, Types, value, jsonMap = new Map(), { definition, setDefinition = noopSetDefinition } = {}) { const typeMap = definition || setDefinition(mapToTypeMap(registry, Types)); const [decoded, decodedLength] = isU8a(value) || isHex(value) ? decodeU8aStruct(registry, new Array(typeMap[0].length), u8aToU8a(value), typeMap) : value instanceof Struct ? [value, 0] : decodeStructFromObject(registry, typeMap, value || {}, jsonMap); super(decoded); this.initialU8aLength = decodedLength; this.registry = registry; this.__internal__jsonMap = jsonMap; this.__internal__Types = typeMap; } static with(Types, jsonMap) { let definition; // eslint-disable-next-line no-return-assign const setDefinition = (d) => definition = d; return class extends Struct { static { const keys = Object.keys(Types); objectProperties(this.prototype, keys, (k, _, self) => self.get(k)); } constructor(registry, value) { super(registry, Types, value, jsonMap, { definition, setDefinition }); } }; } /** * @description The available keys for this struct */ get defKeys() { return this.__internal__Types[1]; } /** * @description Checks if the value is an empty value '{}' */ get isEmpty() { return [...this.keys()].length === 0; } /** * @description The length of the value when encoded as a Uint8Array */ get encodedLength() { let total = 0; for (const v of this.values()) { total += v.encodedLength; } return total; } /** * @description returns a hash of the contents */ get hash() { return this.registry.hash(this.toU8a()); } /** * @description Returns the Type description of the structure */ get Type() { const result = {}; const [Types, keys] = this.__internal__Types; for (let i = 0, count = keys.length; i < count; i++) { result[keys[i]] = new Types[i](this.registry).toRawType(); } return result; } /** * @description Compares the value of the input to see if there is a match */ eq(other) { return compareMap(this, other); } /** * @description Returns a specific names entry in the structure * @param key The name of the entry to retrieve */ get(key) { return super.get(key); } /** * @description Returns the values of a member at a specific index (Rather use get(name) for performance) */ getAtIndex(index) { return this.toArray()[index]; } /** * @description Returns the a types value by name */ getT(key) { return super.get(key); } /** * @description Returns a breakdown of the hex encoding for this Codec */ inspect(isBare) { const inner = []; for (const [k, v] of this.entries()) { inner.push({ ...v.inspect(!isBare || isBoolean(isBare) ? isBare : isBare[k]), name: stringCamelCase(k) }); } return { inner }; } /** * @description Converts the Object to an standard JavaScript Array */ toArray() { return [...this.values()]; } /** * @description Returns a hex string representation of the value */ toHex() { return u8aToHex(this.toU8a()); } /** * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information */ toHuman(isExtended, disableAscii) { const json = {}; for (const [k, v] of this.entries()) { json[k] = v.toHuman(isExtended, disableAscii); } return json; } /** * @description Converts the Object to JSON, typically used for RPC transfers */ toJSON() { const json = {}; for (const [k, v] of this.entries()) { // Here we pull out the entry against the JSON mapping (if supplied) // since this representation goes over RPC and needs to be correct json[(this.__internal__jsonMap.get(k) || k)] = v.toJSON(); } return json; } /** * @description Converts the value in a best-fit primitive form */ toPrimitive(disableAscii) { const json = {}; for (const [k, v] of this.entries()) { json[k] = v.toPrimitive(disableAscii); } return json; } /** * @description Returns the base runtime type name for this instance */ toRawType() { return stringify(typesToMap(this.registry, this.__internal__Types)); } /** * @description Returns the string representation of the value */ toString() { return stringify(this.toJSON()); } /** * @description Encodes the value as a Uint8Array as per the SCALE specifications * @param isBare true when the value has none of the type-specific prefixes (internal) */ toU8a(isBare) { const encoded = []; for (const [k, v] of this.entries()) { encoded.push(v.toU8a(!isBare || isBoolean(isBare) ? isBare : isBare[k])); } return u8aConcatStrict(encoded); } }