/**
 * Checks whether an object conforms to a template.
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
define(["require", "exports"], function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    /**
     * Checks whether a field is required.
     *
     * @param template The template to check.
     *
     * @param name The name of the field.
     *
     * @returns Whether the field is required or not.
     */
    function _required(template, name) {
        const val = template[name];
        if (typeof val === "object") {
            for (const subname in val) {
                if (_required(val, subname)) {
                    return true;
                }
            }
            return false;
        }
        return val;
    }
    function _check(template, toCheck, prefix, ret) {
        for (const name in template) {
            if (_required(template, name)) {
                const prefixed = prefix !== undefined ? [prefix, name].join(".") : name;
                if (!(name in toCheck)) {
                    ret.missing.push(prefixed);
                }
                else {
                    const val = toCheck[name];
                    const templateVal = template[name];
                    if (!(val instanceof Array) && typeof val === "object" &&
                        typeof templateVal === "object") {
                        _check(templateVal, val, prefixed, ret);
                    }
                }
            }
        }
        for (const name in toCheck) {
            if (!(name in template)) {
                const prefixed = prefix !== undefined ? [prefix, name].join(".") : name;
                ret.extra.push(prefixed);
            }
        }
    }
    /**
     * Checks whether an object conforms to a template. The template must be an
     * object which specifies the known fields and which among them are required. A
     * field is known if it appears in the template. A field is considered
     * *required* if:
     *
     *  + it is an object which has any field which is required, or
     *
     *  + it is not an object but evaluates to a true value.
     *
     * A required field which does not appear in the object being checked will
     * appear in the ``missing`` field in the returned value.
     *
     * A field which appears on the object being checked but which is not known will
     * appear in the ``extra`` field in the returned value.
     *
     * The fields mentioned above exist only if there is something to report. The
     * names returned in the lists are fully qualified names.
     *
     * For instance, given this template:
     *
     *     {
     *         foo: false,
     *         bar: {
     *             baz: true,
     *             bin: false,
     *         },
     *         bip: {
     *             baz: false,
     *             bin: false,
     *         }
     *     }
     *
     * The names "foo", "bar", "bar.baz", "bar.bin", "bip", "bip.baz", bip.bin" are
     * known. The names "bar" and "bar.baz" are required. The name "bar" is required
     * because "bar.baz" is required. The other names correspond to objects whose
     * fields are not required or are non-object values that evaluate to false.
     *
     * @param template The template to use for the check.
     *
     * @param toCheck The object to check
     *
     * @returns The results.
     */
    function check(template, toCheck) {
        const initial = { missing: [], extra: [] };
        _check(template, toCheck, undefined, initial);
        // clean up
        // tslint:disable-next-line:no-any
        const ret = initial;
        for (const name in ret) {
            if (ret[name].length === 0) {
                delete ret[name];
            }
        }
        return ret;
    }
    exports.check = check;
    /**
     * Check whether the object fits the template, and throw at the first sign of
     * trouble. The thrown object contains information about the first error
     * encountered.
     *
     * @param template The template to use for the check.
     *
     * @param toCheck The object to check
     *
     * @throws {Error} If there is any error.
     */
    function assertSummarily(template, toCheck) {
        const result = check(template, toCheck);
        if (result.missing !== undefined) {
            throw new Error(`missing option: ${result.missing[0]}`);
        }
        if (result.extra !== undefined) {
            throw new Error(`extra option: ${result.extra[0]}`);
        }
    }
    exports.assertSummarily = assertSummarily;
    /**
     * Check whether the object fits the template, and throw an error that reports
     * all issues.
     *
     * @param template The template to use for the check.
     *
     * @param toCheck The object to check
     *
     * @throws {Error} If there is any error.
     */
    function assertExtensively(template, toCheck) {
        const result = check(template, toCheck);
        const errors = [];
        if (result.missing !== undefined) {
            for (const name of result.missing) {
                errors.push(`missing option: ${name}`);
            }
        }
        if (result.extra !== undefined) {
            for (const name of result.extra) {
                errors.push(`extra option: ${name}`);
            }
        }
        if (errors.length !== 0) {
            throw new Error(errors.join(", "));
        }
    }
    exports.assertExtensively = assertExtensively;
});
//  LocalWords:  MPL baz bip
//# sourceMappingURL=object-check.js.map