Manual Reference Source

app/modules/utils/utils.js

// @flow
const _ = require('lodash');
const Errors = require('../exceptions/errors');


function hasProperty(obj: Object, key: string | number): boolean {
    return Object.prototype.hasOwnProperty.call(obj, key);
}

function _return_inner_object(object: ? Object, copy: boolean = true): any {
    if (object == null) {
        return null;
    } else if (typeof object === 'object') {
        if (copy) {
            return _.cloneDeep(object);
        }
        return object;
    }
    return object;
}

function _test_inner_object(object: ? Object, key: string | number): Array<any> {
    if (!isNaN(parseInt(key, 10))) {
        key = parseInt(key, 10);
    }

    if (object == null) {
        return [key, null];
    } else if (object instanceof Array) {
        if (object.length <= key) {
            return [key, null];
        }
    } else if (!hasProperty(object, key)) {
        return [key, null];
    }
    return [key, object];
}


function find_object_with_path(object: ? Object, path: Array<string>): any {
    const p = path;

    if (p.length === 0) {
        return _return_inner_object(object, false); // Don't copy
    }

    if (p.length > 1) {
        let key = p[0];
        let result = null;
        [key, result] = _test_inner_object(object, key);
        if (result == null) {
            return result;
        }
        if (object == null) {
            return object;
        }
        return find_object_with_path(object[key], p.slice(1));
    }
    return find_object_with_path(object, p.slice(1));
}

function find_value_with_path(object: ?Object, path: Array<string>): any {
    const p = path;
    if (p.length === 0) {
        return _return_inner_object(object);
    }

    let key = p[0];
    let result = null;
    [key, result] = _test_inner_object(object, key);
    if (result == null) {
        return result;
    }
    if (object == null) {
        return object;
    }
    return find_value_with_path(object[key], p.slice(1));
}

function* find_popvalue_with_path(object: ?Object, path: Array<string>,
    return_object: boolean = false, keep_null: boolean = false): any {
    const p = path;
    if (p.length === 0) {
        const info = _return_inner_object(object, !return_object);
        if (return_object && info instanceof Array) {
            yield* info;
        } else if (info != null) {
            yield info;
        } else if (keep_null) {
            yield null;
        }
    } else {
        let key = p[0];
        const is_nan = isNaN(parseInt(key, 10));
        if (!is_nan) {
            key = parseInt(key, 10);
        }

        if (object instanceof Array) {
            if (is_nan) {
                for (const i in object) {
                    yield* find_popvalue_with_path(object[i], p, return_object, keep_null);
                }
            } else if (key < object.length) {
                if (return_object && p.length === 1) {
                    yield* find_popvalue_with_path(object, p.slice(1), return_object, keep_null);
                } else {
                    yield* find_popvalue_with_path(object[key], p.slice(1), return_object, keep_null);
                }
            }
        } else if (object != null && hasProperty(object, key)) {
            if (return_object && p.length === 1) {
                yield* find_popvalue_with_path(object, p.slice(1), return_object, keep_null);
            } else {
                yield* find_popvalue_with_path(object[key], p.slice(1), return_object, keep_null);
            }
        } else if (keep_null) {
            yield null;
        }
    }
}


function forge_whitelist_blacklist_query(lists: Object): Object {
    let {
        whitelist,
        blacklist,
    } = lists;
    if (whitelist == null) {
        whitelist = new Set([]);
    }
    if (blacklist == null) {
        blacklist = new Set([]);
    }

    let query = {};
    if (whitelist.size === 0 && blacklist.size === 0) {
        query = {};
    } else if (whitelist.size === 0 && blacklist.size > 0) {
        query = {
            _id: {
                $nin: [...blacklist],
            },
        };
    } else if (blacklist.size === 0 && whitelist.size > 0) {
        query = {
            _id: {
                $in: [...whitelist],
            },
        };
    } else {
        query = {
            _id: {
                $in: [...whitelist],
                $nin: [...blacklist],
            },
        };
    }
    return query;
}


function merge_with_replacement(object: Object, ...sources): Object {
    function customizer(objValue, srcValue) {
        if (srcValue == null) {
            return objValue;
        }

        if (_.isArray(objValue) && srcValue != null) {
            objValue = srcValue;
            return objValue;
        }
    }
    return _.mergeWith(object, ...sources, customizer);
}

function merge_with_concat(object: Object, ...sources) {
    function customizer(objValue, srcValue) {
        if (_.isArray(objValue)) {
            return objValue.concat(srcValue);
        }
    }
    return _.mergeWith(object, ...sources, customizer);
}

function merge_with_superposition(object: Object, ...sources) {
    function customizer(objValue, srcValue) {
        if (_.isArray(objValue)) {
            if (_.isArray(srcValue)) {
                const larger = srcValue.length > objValue.length ? srcValue : objValue;
                const smaller = srcValue.length > objValue.length ? objValue : srcValue;
                return larger.map((o, i) => {
                    if (i < smaller.length) {
                        return _.merge(o, smaller[i]);
                    }
                    return o;
                });
            }

            if (srcValue) {
                return objValue.concat(srcValue);
            }
            return objValue;
        }
    }
    return _.mergeWith(object, ...sources, customizer);
}

function make_nested_object_from_path(path: Array<string>,
    value: any, obj: Object = {}): Object {
    const rpath = _.reverse(path);
    return rpath.reduce((acc, field) => {
        if (Object.keys(acc).length === 0) {
            if (field === '*') {
                if (value instanceof Array) {
                    return value;
                }
                return [value];
            }
            acc[field] = value;
            return acc;
        }

        if (field === '*') {
            return [acc];
        }
        const my_obj = {};
        my_obj[field] = acc;
        return my_obj;
    }, obj);
}

async function traverse_recreate_and_execute(object: Object, path: Array<string>,
        f: Function, keep_last: boolean = true): any {
    if (path.length === 0) {
        const info = _return_inner_object(object);
        const result = await f(info);
        return result;
    }

    const key = path[0];
    const idx = parseInt(key, 10);

    if (object instanceof Array) {
        if (isNaN(idx)) {
            const new_array = [];
            for (const i in object) {
                const result = await traverse_recreate_and_execute(object[i], path, f, keep_last);
                new_array.push(result);
            }
            return new_array;
        } else if (key < object.length) {
            const result = await traverse_recreate_and_execute(object[key],
                path.slice(1), f, keep_last);
            return [result];
        }
    } else if (object != null && hasProperty(object, key)) {
        const result = await traverse_recreate_and_execute(object[key], path.slice(1), f, keep_last);
        if (path.length === 1 && !keep_last) {
            return result;
        }
        return { [key]: result };
    } else {
        if (keep_last) {
            return { [key]: null };
        }
        return null;
    }
}

async function traverse_and_execute(object: Object, path: Array<string>, f: Function): any {
    if (path.length === 0) {
        const info = _return_inner_object(object);
        const result = await f(info);
        return result;
    }

    const key = path[0];
    const idx = parseInt(key, 10);

    if (object instanceof Array) {
        if (isNaN(idx)) {
            for (const i in object) {
                object[i] = await traverse_and_execute(object[i], path, f);
            }
            return object;
        } else if (key < object.length) {
            object[key] = await traverse_and_execute(object[key],
                    path.slice(1), f);
            return object;
        }
    } else if (object != null && hasProperty(object, key)) {
        const result = await traverse_and_execute(object[key], path.slice(1), f);
        object[key] = result;
        return object;
    }
    return object;
}

function isNil(path: string, object: ?Object): boolean {
    if (_.isNil(object)) {
        return true;
    }
    return _.isNil(find_value_with_path(object, path.split('.')));
}

function filter_empty_or_null_objects(array: Array<any>): Array<any> {
    return array.filter((obj) => {
        if (_.isNil(obj) || _.isEmpty(obj)) {
            return false;
        }

        const filtered = _.filter(obj, val => !_.isNil(val));

        return !_.isEmpty(filtered);
    });
}


module.exports = {
    hasProperty,
    find_value_with_path,
    find_object_with_path,
    forge_whitelist_blacklist_query,
    merge_with_replacement,
    merge_with_concat,
    merge_with_superposition,
    find_popvalue_with_path,
    traverse_and_execute,
    traverse_recreate_and_execute,
    make_nested_object_from_path,
    isNil,
    filter_empty_or_null_objects,
};