All files / src/serializer serializable.ts

90.74% Statements 49/54
83.33% Branches 25/30
100% Functions 7/7
90.74% Lines 49/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193                                                                                                                                                                          9152x 9152x 9152x 9152x 9152x   32017x 7666x   24351x   32017x 5348x     5348x     32017x 284x   32017x 32x   32017x   9152x 17562x   17562x     17562x 1464x   16098x 16098x 72x       16026x 9313x 30856x         9313x         9313x 6713x 220x       220x   16098x   9152x 9152x       6170x 6170x 22x     22x     6170x 1x   6170x     6170x       8x               5561x 2394x   3167x 3161x   6x              
import {ABI} from '../chain'
import {ABIDecoder} from './decoder'
import {ABIEncoder} from './encoder'
 
/** A self-describing object that can be ABI encoded and decoded. */
export type ABISerializable =
    | ABISerializableObject
    | string
    | boolean
    | ABISerializable[]
    | {[key: string]: ABISerializable}
 
/** Type describing an ABI type, either a string (e.g. `uint32[]`) or a ABI type class. */
export type ABISerializableType = string | ABISerializableConstructor | ABITypeDescriptor
 
/** Interface that should be implemented by ABI serializable objects. */
export interface ABISerializableObject {
    /** Called when encoding to binary abi format. */
    toABI?(encoder: ABIEncoder): void
    /** Called when encoding to json abi format. */
    toJSON(): any
    /** Return true if the object equals the other object passed. */
    equals(other: any): boolean
}
 
export interface ABITypeModifiers {
    /** Type is optional, defaults to false. */
    optional?: boolean
    /** Type is an array, defaults to false. */
    array?: boolean
    /** Type has a fixed array size, defaults to undefined */
    size?: number
    /** Type is a binary extension, defaults to false. */
    extension?: boolean
}
 
export interface ABITypeDescriptor extends ABITypeModifiers {
    /** Type name or class. */
    type: ABISerializableConstructor | string
}
 
export interface ABIField extends ABITypeDescriptor {
    /** Field name. */
    name: string
}
 
export interface ABISerializableConstructor {
    /** Name of the type, e.g. `asset`. */
    abiName: string
    /** For structs, the fields that this type contains. */
    abiFields?: ABIField[]
    /** For structs, the base class this type extends. */
    abiBase?: ABISerializableConstructor
    /** For variants, the different types this type can represent. */
    abiVariant?: ABITypeDescriptor[]
    /** Alias to another type. */
    abiAlias?: ABITypeDescriptor
    /** Return value to use when creating a new instance of this type, used when decoding binary extensions. */
    abiDefault?: () => ABISerializable
    /**
     * Create new instance from JavaScript object.
     * Should also accept an instance of itself and return that unchanged.
     */
    from(value: any): ABISerializable
    /**
     * Create instance from binary ABI data.
     * @param decoder Decoder instance to read from.
     */
    fromABI?(decoder: ABIDecoder): ABISerializable
    /**
     * Static ABI encoding can be used to encode non-class types.
     * Will be used in favor of instance.toABI if both exists.
     * @param value The value to encode.
     * @param encoder The encoder to write the value to.
     */
    toABI?(value: any, encoder: ABIEncoder): void
    /**
     * Create a new instance, don't use this other than from a custom `from` factory method.
     * @internal
     */
    new (...args: any[]): ABISerializableObject
}
 
/** Return a ABI definition for given ABISerializableType. */
export function synthesizeABI(type: ABISerializableConstructor) {
    const structs: ABI.Struct[] = []
    const variants: ABI.Variant[] = []
    const aliases: ABI.TypeDef[] = []
    const seen = new Set<ABISerializableConstructor>()
    const resolveAbiType = (t: ABITypeDescriptor) => {
        let typeName: string
        if (typeof t.type !== 'string') {
            typeName = resolve(t.type)
        } else {
            typeName = t.type
        }
        if (t.array === true) {
            Iif (t.size) {
                typeName += `[${t.size}]`
            } else {
                typeName += '[]'
            }
        }
        if (t.optional === true) {
            typeName += '?'
        }
        if (t.extension === true) {
            typeName += '$'
        }
        return typeName
    }
    const resolve = (t: ABISerializableConstructor) => {
        Iif (!t.abiName) {
            throw new Error('Encountered non-conforming type')
        } else Iif (t.abiName === '__struct') {
            throw new Error('Misconfigured Struct subclass, did you forget @Struct.type?')
        }
        if (seen.has(t)) {
            return t.abiName
        }
        seen.add(t)
        if (t.abiAlias) {
            aliases.push({
                new_type_name: t.abiName,
                type: resolveAbiType(t.abiAlias),
            })
        } else if (t.abiFields) {
            const fields = t.abiFields.map((field) => {
                return {
                    name: field.name,
                    type: resolveAbiType(field),
                }
            })
            const struct: ABI.Struct = {
                base: t.abiBase ? resolve(t.abiBase) : '',
                name: t.abiName,
                fields,
            }
            structs.push(struct)
        } else if (t.abiVariant) {
            const variant: ABI.Variant = {
                name: t.abiName,
                types: t.abiVariant.map(resolveAbiType),
            }
            variants.push(variant)
        }
        return t.abiName
    }
    const root = resolve(type)
    return {abi: ABI.from({structs, variants, types: aliases}), types: Array.from(seen), root}
}
 
export function abiTypeString(type: ABITypeDescriptor) {
    let typeName = typeof type.type === 'string' ? type.type : type.type.abiName
    if (type.array === true) {
        Iif (type.size) {
            typeName += `[${type.size}]`
        } else {
            typeName += '[]'
        }
    }
    if (type.optional === true) {
        typeName += '?'
    }
    Iif (type.extension === true) {
        typeName += '$'
    }
    return typeName
}
 
export function isTypeDescriptor(type: ABISerializableType): type is ABITypeDescriptor {
    return (
        typeof type !== 'string' &&
        (type as any).abiName === undefined &&
        (type as any).type !== undefined
    )
}
 
export function toTypeDescriptor(type: ABISerializableType): ABITypeDescriptor {
    if (typeof type === 'string') {
        return {type}
    }
    if (typeof (type as ABISerializableConstructor).abiName !== 'undefined') {
        return {type: type as ABISerializableConstructor}
    }
    return type as ABITypeDescriptor
}
 
/** Returns true if the given value conforms to ABISerializableObject. */
export function isABISerializableObject(value: any): value is ABISerializableObject {
    return value && value.constructor && typeof value.constructor.abiName === 'string'
}