Source: vsa/unions.js

/**
 * @module {Object} unions
 * @description Unions for Typed Maps and Types
 */
const convertTypeToTypedJson = require('./convertTypedMapToTypedJson');
const isType = require('../util/isType');
const str = require('../util/str');

/**
 * Union two Typed Maps based on Keys and Types
 *
 * @param {Map} typedMap1 - Map to Union
 * @param {Map} typedMap2 - Map to Union
 * @returns {Map} Unioned Map
 * @throws Throws an error if an error occurred when unioning Typed Maps
 */
function unionTypedMaps(typedMap1, typedMap2) {
    try {
        const keys = new Set([...typedMap1.keys(), ...typedMap2.keys()]);
        const unionedTypeMap = new Map();
        for (const key of keys) {
            const inT1 = typedMap1.get(key);
            const inT2 = typedMap2.get(key);
            if (inT1 !== undefined && inT2 !== undefined) {
                const t1Prefix = inT1.prefix.join('.');
                const t2Prefix = inT2.prefix.join('.');
                if (t1Prefix !== t2Prefix) {
                    throw new Error(`Prefixes "${t1Prefix}" and "${t2Prefix}" for Key "${key}" do not match`);
                }
                const type = unionTypes(inT1.type, inT2.type);
                if (!isType.isNull(type)) {
                    unionedTypeMap.set(key, {
                        prefix: inT1.prefix,
                        type
                    });
                }
            } else if (inT1 !== undefined) {
                unionedTypeMap.set(key, inT1);
            } else if (inT2 !== undefined) {
                unionedTypeMap.set(key, inT2);
            } else {
                throw new Error(`Key "${key}" is nonexistent`);
            }
        }
        return unionedTypeMap;
    } catch (error) {
        throw new Error(
            `${error} when unioning Typed Maps:\n` +
            `Typed Map 1:\n` +
            `${str(convertTypeToTypedJson(typedMap1))}\n` +
            `Typed Map 2:\n` +
            `${str(convertTypeToTypedJson(typedMap2))}`
        );
    }
}

/**
 * Union two Types based on Types
 *
 * @param {Object} type1 - Type to Union (array, object, types)
 * @param {Object} type2 - Type to Union (array, object, types)
 * @returns {(Object|null)} Unioned Type if not null
 */
function unionTypes(type1, type2) {

    const unionedType = {
        array: null,
        object: null,
        types: null
    };

    // array
    if (!isType.isNull(type1.array) && !isType.isNull(type2.array)) {
        unionedType.array = unionTypes(type1.array, type2.array);
    } else if (!isType.isNull(type1.array)) {
        unionedType.array = type1.array;
    } else if (!isType.isNull(type2.array)) {
        unionedType.array = type2.array;
    }

    // object
    if (!isType.isNull(type1.object) && !isType.isNull(type2.object)) {
        unionedType.object = unionTypedMaps(type1.object, type2.object);
    } else if (!isType.isNull(type1.object)) {
        unionedType.object = type1.object;
    } else if (!isType.isNull(type2.object)) {
        unionedType.object = type2.object;
    }

    // types
    if (!isType.isNull(type1.types) && !isType.isNull(type2.types)) {
        unionedType.types = new Set([...type1.types, ...type2.types]);
    } else if (!isType.isNull(type1.types)) {
        unionedType.types = type1.types;
    } else if (!isType.isNull(type2.types)) {
        unionedType.types = type2.types;
    }

    // null if input types are null
    if (isType.isNull(unionedType.array) &&
        isType.isNull(unionedType.object) &&
        isType.isNull(unionedType.types)) {
        return null
    }

    return unionedType;
}

module.exports = {
    unionTypedMaps,
    unionTypes
};