All files / src/chain struct.ts

97.67% Statements 42/43
85% Branches 17/20
100% Functions 11/11
97.67% Lines 42/43

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                                1x             1006x   695x   311x 178x   133x       752x 752x 887x 135x   887x 3165x     752x 752x         695x 695x 2896x                     4x 4x             4x         57x 57x 57x 269x   57x       1x 1x 1x 83x 83x 83x     1x       340x 340x 340x 78x 78x 262x   4x 4x 4x   340x        
import {
    ABIField,
    ABISerializableConstructor,
    ABISerializableObject,
    ABITypeModifiers,
} from '../serializer/serializable'
import {abiDecode, Resolved} from '../serializer/decoder'
import {abiEncode} from '../serializer/encoder'
import {isInstanceOf} from '../utils'
 
export interface StructConstructor extends ABISerializableConstructor {
    new <T extends Struct>(...args: any[]): T
    structFields: ABIField[]
}
 
export class Struct implements ABISerializableObject {
    static abiName = '__struct'
    static abiFields: ABIField[]
    static abiBase: ABISerializableConstructor
 
    static from<T extends StructConstructor>(this: T, value: any): InstanceType<T>
    static from(value: any): unknown
    static from(value: any) {
        if (value[Resolved] === true) {
            // objects already resolved
            return new this(value)
        }
        if (isInstanceOf(value, this)) {
            return value
        }
        return abiDecode({object: value, type: this})
    }
 
    static get structFields() {
        const rv: ABIField[] = []
        const walk = (t: ABISerializableConstructor) => {
            if (t.abiBase) {
                walk(t.abiBase)
            }
            for (const field of t.abiFields || []) {
                rv.push(field)
            }
        }
        walk(this)
        return rv
    }
 
    /** @internal */
    constructor(object: any) {
        const self = this.constructor as typeof Struct
        for (const field of self.structFields) {
            this[field.name] = object[field.name]
        }
    }
 
    /**
     * Return true if this struct equals the other.
     *
     * Note: This compares the ABI encoded bytes of both structs, subclasses
     *       should implement their own fast equality check when possible.
     */
    equals(other: any): boolean {
        const self = this.constructor as typeof Struct
        Iif (
            other.constructor &&
            typeof other.constructor.abiName === 'string' &&
            other.constructor.abiName !== self.abiName
        ) {
            return false
        }
        return abiEncode({object: this}).equals(abiEncode({object: self.from(other) as any}))
    }
 
    /** @internal */
    toJSON() {
        const self = this.constructor as typeof Struct
        const rv: any = {}
        for (const field of self.structFields) {
            rv[field.name] = this[field.name]
        }
        return rv
    }
}
 
export namespace Struct {
    const FieldsOwner = Symbol('FieldsOwner')
    export function type(name: string) {
        return function <T extends StructConstructor>(struct: T) {
            struct.abiName = name
            return struct
        }
    }
    export function field(
        type: ABISerializableConstructor | string,
        options: ABITypeModifiers = {}
    ) {
        return <T extends Struct>(target: T, name: string) => {
            const ctor = target.constructor as StructConstructor
            if (!ctor.abiFields) {
                ctor.abiFields = []
                ctor.abiFields[FieldsOwner] = ctor
            } else if (ctor.abiFields[FieldsOwner] !== ctor) {
                // if the target class isn't the owner we set the base and start new fields
                ctor.abiBase = ctor.abiFields[FieldsOwner]
                ctor.abiFields = []
                ctor.abiFields[FieldsOwner] = ctor
            }
            ctor.abiFields.push({...options, name, type})
        }
    }
}