UNPKG

@polkadot/types-codec

Version:
233 lines (232 loc) 7.48 kB
import { identity, isCodec, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot/util'; import { typeToConstructor } from '../utils/index.js'; import { Null } from './Null.js'; class None extends Null { /** * @description Returns the base runtime type name for this instance */ toRawType() { return 'None'; } } /** @internal */ function decodeOption(registry, Type, value) { if (value instanceof Type) { // don't re-create, use as it (which also caters for derived types) return value; } else if (value instanceof Option) { if (value.value instanceof Type) { // same instance, return it return value.value; } else if (value.isNone) { // internal is None, we are also none return new None(registry); } // convert the actual value into known return new Type(registry, value.value); } else if (isNull(value) || isUndefined(value) || value === '0x' || value instanceof None) { // anything empty we pass as-is return new None(registry); } else if (isU8a(value)) { // the isU8a check happens last in the if-tree - since the wrapped value // may be an instance of it, so Type and Option checks go in first return !value.length || value[0] === 0 ? new None(registry) : new Type(registry, value.subarray(1)); } return new Type(registry, value); } /** * @name Option * @description * An Option is an optional field. Basically the first byte indicates that there is * is value to follow. If the byte is `1` there is an actual value. So the Option * implements that - decodes, checks for optionality and wraps the required structure * with a value if/as required/found. */ export class Option { registry; createdAtHash; initialU8aLength; isStorageFallback; __internal__Type; __internal__raw; constructor(registry, typeName, value, { definition, setDefinition = identity } = {}) { const Type = definition || setDefinition(typeToConstructor(registry, typeName)); const decoded = isU8a(value) && value.length && !isCodec(value) ? value[0] === 0 ? new None(registry) : new Type(registry, value.subarray(1)) : decodeOption(registry, Type, value); this.registry = registry; this.__internal__Type = Type; this.__internal__raw = decoded; if (decoded?.initialU8aLength) { this.initialU8aLength = 1 + decoded.initialU8aLength; } } static with(Type) { let definition; const setDefinition = (d) => { definition = d; return d; }; return class extends Option { constructor(registry, value) { super(registry, Type, value, { definition, setDefinition }); } }; } /** * @description The length of the value when encoded as a Uint8Array */ get encodedLength() { // boolean byte (has value, doesn't have) along with wrapped length return 1 + this.__internal__raw.encodedLength; } /** * @description returns a hash of the contents */ get hash() { return this.registry.hash(this.toU8a()); } /** * @description Checks if the Option has no value */ get isEmpty() { return this.isNone; } /** * @description Checks if the Option has no value */ get isNone() { return this.__internal__raw instanceof None; } /** * @description Checks if the Option has a value */ get isSome() { return !this.isNone; } /** * @description The actual value for the Option */ get value() { return this.__internal__raw; } /** * @description Compares the value of the input to see if there is a match */ eq(other) { if (other instanceof Option) { return (this.isSome === other.isSome) && this.value.eq(other.value); } return this.value.eq(other); } /** * @description Returns a breakdown of the hex encoding for this Codec */ inspect() { if (this.isNone) { return { outer: [new Uint8Array([0])] }; } const { inner, outer = [] } = this.__internal__raw.inspect(); return { inner, outer: [new Uint8Array([1]), ...outer] }; } /** * @description Returns a hex string representation of the value */ toHex() { // This attempts to align with the JSON encoding - actually in this case // the isSome value is correct, however the `isNone` may be problematic return this.isNone ? '0x' : u8aToHex(this.toU8a().subarray(1)); } /** * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information */ toHuman(isExtended, disableAscii) { return this.__internal__raw.toHuman(isExtended, disableAscii); } /** * @description Converts the Object to JSON, typically used for RPC transfers */ toJSON() { return this.isNone ? null : this.__internal__raw.toJSON(); } /** * @description Converts the value in a best-fit primitive form */ toPrimitive(disableAscii) { return this.isNone ? null : this.__internal__raw.toPrimitive(disableAscii); } /** * @description Returns the base runtime type name for this instance */ toRawType(isBare) { const wrapped = this.registry.getClassName(this.__internal__Type) || new this.__internal__Type(this.registry).toRawType(); return isBare ? wrapped : `Option<${wrapped}>`; } /** * @description Returns the string representation of the value */ toString() { return this.__internal__raw.toString(); } /** * @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) { if (isBare) { return this.__internal__raw.toU8a(true); } const u8a = new Uint8Array(this.encodedLength); if (this.isSome) { u8a.set([1]); u8a.set(this.__internal__raw.toU8a(), 1); } return u8a; } /** * @description Returns the value that the Option represents (if available), throws if null */ unwrap() { if (this.isNone) { throw new Error('Option: unwrapping a None value'); } return this.__internal__raw; } /** * @description Returns the value that the Option represents (if available) or defaultValue if none * @param defaultValue The value to return if the option isNone */ unwrapOr(defaultValue) { return this.isSome ? this.unwrap() : defaultValue; } /** * @description Returns the value that the Option represents (if available) or defaultValue if none * @param defaultValue The value to return if the option isNone */ unwrapOrDefault() { return this.isSome ? this.unwrap() : new this.__internal__Type(this.registry); } }