Home Reference Source

lib/serializer/src/serializer.js

import ByteBuffer from "bytebuffer";
import EC from "./error_with_cause";
const Buffer = require("safe-buffer").Buffer;

const HEX_DUMP = process.env.npm_config__graphene_serializer_hex_dump;

class Serializer {
    constructor(operation_name, types) {
        this.operation_name = operation_name;
        this.types = types;
        if (this.types) this.keys = Object.keys(this.types);

        Serializer.printDebug = true;
    }

    fromByteBuffer(b) {
        var object = {};
        var field = null;
        try {
            var iterable = this.keys;
            for (var i = 0, field; i < iterable.length; i++) {
                field = iterable[i];
                var type = this.types[field];
                try {
                    if (HEX_DUMP) {
                        if (type.operation_name) {
                            console.error(type.operation_name);
                        } else {
                            var o1 = b.offset;
                            type.fromByteBuffer(b);
                            var o2 = b.offset;
                            b.offset = o1;
                            //b.reset()
                            var _b = b.copy(o1, o2);
                            console.error(
                                `${this.operation_name}.${field}\t`,
                                _b.toHex()
                            );
                        }
                    }
                    object[field] = type.fromByteBuffer(b);
                } catch (e) {
                    if (Serializer.printDebug) {
                        console.error(
                            `Error reading ${
                                this.operation_name
                            }.${field} in data:`
                        );
                        b.printDebug();
                    }
                    throw e;
                }
            }
        } catch (error) {
            EC.throw(this.operation_name + "." + field, error);
        }

        return object;
    }

    appendByteBuffer(b, object) {
        var field = null;
        try {
            var iterable = this.keys;
            for (var i = 0, field; i < iterable.length; i++) {
                field = iterable[i];
                var type = this.types[field];
                type.appendByteBuffer(b, object[field]);
            }
        } catch (error) {
            try {
                EC.throw(
                    this.operation_name +
                        "." +
                        field +
                        " = " +
                        JSON.stringify(object[field]),
                    error
                );
            } catch (e) {
                // circular ref
                EC.throw(
                    this.operation_name + "." + field + " = " + object[field],
                    error
                );
            }
        }
        return;
    }

    fromObject(serialized_object) {
        var result = {};
        var field = null;
        try {
            var iterable = this.keys;
            for (var i = 0, field; i < iterable.length; i++) {
                field = iterable[i];
                var type = this.types[field];
                var value = serialized_object[field];
                //DEBUG value = value.resolve if value.resolve
                //DEBUG console.log('... value',field,value)
                var object = type.fromObject(value);
                result[field] = object;
            }
        } catch (error) {
            EC.throw(this.operation_name + "." + field, error);
        }

        return result;
    }

    /**
        @arg {boolean} [debug.use_default = false] - more template friendly
        @arg {boolean} [debug.annotate = false] - add user-friendly information
    */
    toObject(
        serialized_object = {},
        debug = {use_default: false, annotate: false}
    ) {
        var result = {};
        var field = null;
        try {
            if (!this.types) return result;

            var iterable = this.keys;
            for (var i = 0, field; i < iterable.length; i++) {
                field = iterable[i];
                var type = this.types[field];
                var object = type.toObject(
                    typeof serialized_object !== "undefined" &&
                    serialized_object !== null
                        ? serialized_object[field]
                        : undefined,
                    debug
                );
                result[field] = object;
                if (HEX_DUMP) {
                    var b = new ByteBuffer(
                        ByteBuffer.DEFAULT_CAPACITY,
                        ByteBuffer.LITTLE_ENDIAN
                    );
                    type.appendByteBuffer(
                        b,
                        typeof serialized_object !== "undefined" &&
                        serialized_object !== null
                            ? serialized_object[field]
                            : undefined
                    );
                    b = b.copy(0, b.offset);
                    console.error(this.operation_name + "." + field, b.toHex());
                }
            }
        } catch (error) {
            EC.throw(this.operation_name + "." + field, error);
        }

        return result;
    }

    /** Sort by the first element in a operation */
    compare(a, b) {
        let first_key = this.keys[0];
        let first_type = this.types[first_key];

        let valA = a[first_key];
        let valB = b[first_key];

        if (first_type.compare) return first_type.compare(valA, valB);

        if (typeof valA === "number" && typeof valB === "number")
            return valA - valB;

        let encoding;
        if (Buffer.isBuffer(valA) && Buffer.isBuffer(valB)) {
            // A binary string compare does not work.  If localeCompare is well supported that could replace HEX.  Performanance is very good so comparing HEX works.
            encoding = "hex";
        }

        let strA = valA.toString(encoding);
        let strB = valB.toString(encoding);
        return strA > strB ? 1 : strA < strB ? -1 : 0;
    }

    // <helper_functions>

    fromHex(hex) {
        var b = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN);
        return this.fromByteBuffer(b);
    }

    fromBuffer(buffer) {
        var b = ByteBuffer.fromBinary(
            buffer.toString("binary"),
            ByteBuffer.LITTLE_ENDIAN
        );
        return this.fromByteBuffer(b);
    }

    toHex(object) {
        // return this.toBuffer(object).toString("hex")
        var b = this.toByteBuffer(object);
        return b.toHex();
    }

    toByteBuffer(object) {
        var b = new ByteBuffer(
            ByteBuffer.DEFAULT_CAPACITY,
            ByteBuffer.LITTLE_ENDIAN
        );
        this.appendByteBuffer(b, object);
        return b.copy(0, b.offset);
    }

    toBuffer(object) {
        return Buffer.from(this.toByteBuffer(object).toBinary(), "binary");
    }
}

export default Serializer;