src/hooks/runHookDirectly.js

Summary

Maintainability
B
5 hrs
Test Coverage
import { magenta, underline } from 'chalk';

import log from '../log/default';
import isValid from '../validation/helpers/isValid';
import throwValidationError from '../validation/helpers/throwValidationError';
import { getContext } from '../context/helpers/manageContext';
import objectToArray from '../helpers/objectToArray';

import { getActions } from './manageActions';

/**
 * Used to invoke a hook directly without needing to initialize using registerHooks.
 *
 * @example
 * // First function takes the extension name, the second takes the hook name and possible
 * // arguments and the third will take a callback that will receive a single value.
 * // The callback is expected if "hasCallback" was set to true.
 * (extensionName) => (hookName, ...arguments) => (potentialCallback)
 *
 * @param {Object} hookMeta - Meta data related to a hook.
 * @param {Object[]} args - Arguments to send to an action.
 * @param {function} callback - Callback to be used for the response of the action.
 *
 * @returns {function} - Will return a function that takes in the name of the hook and potential arguments.
 */
export default function runHookDirectly({
        arguments: argumentsDefinitions,
        description,
        extension,
        initialValue,
        name,
        returns,
    }, args = [], callback) {
    // Validate args
    if (argumentsDefinitions) {
        const argumentsDefinitionsArray = objectToArray(argumentsDefinitions);
        args.forEach((value, i) => {
            // If we have meta data for this argument, this means that we will allow
            // for more arguments than we have validation for.
            if (argumentsDefinitionsArray[i] && argumentsDefinitionsArray[i].validator) {
                const validationResult = isValid(value, argumentsDefinitionsArray[i].validator);
                if (validationResult !== true) {
                    try {
                        throwValidationError(argumentsDefinitionsArray[i].name, validationResult, value, 'argument');
                    } catch (err) {
                        log.large.error(
                            `An argument was not valid in ${underline(name)} from ${underline(extension)}.` +
                                `\n\n${err.message}`,
                            'Hook problem'
                        );
                    }
                }
            }
        });
    }

    let previousValue = initialValue;
    const postActions = [];

    getActions().forEach(({ name: actionExtensionName, actions }) => {
        actions.forEach((action) => {
            // Only run if no connection is made to a hook/extension or if they match
            if ((!action.extension || action.extension === extension) &&
                (!action.hook || action.hook === name)) {
                const initAction = (currentAction) => (post = false) => {
                    // Run actions in a semi-managed way
                    let step = 'first';
                    try {
                        const createAction = currentAction({
                            context: getContext(),
                            description,
                            extension,
                            hook: name,
                            previousValue,
                        });
                        step = 'second';

                        if (createAction) {
                            const performAction = createAction(...args);
                            step = 'third';

                            if (performAction) {
                                if (getContext().verbose) {
                                    const isPost = post ? '[Post] ' : '';
                                    log.small.log(
                                        `${magenta('Hook')} - ` +
                                        `${isPost}Running hook defined in ${underline(extension)} ` +
                                        `named ${underline(name)} with action from ${underline(actionExtensionName)}`
                                    );
                                }

                                previousValue = performAction(previousValue);

                                if (returns) {
                                    const validationResult = isValid(previousValue, returns);
                                    if (validationResult !== true) {
                                        try {
                                            throwValidationError(
                                                `action in ${actionExtensionName} for ${name}`,
                                                validationResult,
                                                previousValue,
                                                'return value of'
                                            );
                                        } catch (err) {
                                            log.large.error(
                                                `A return value was not valid.\n\n${err.message}`,
                                                'Hook problem'
                                            );
                                        }
                                    }
                                }

                                if (callback) {
                                    callback(previousValue);
                                }
                            }
                        }
                    } catch (error) {
                        if (getContext().verbose) {
                            const isPost = post ? '[Post] ' : '';
                            log.small.warn(
                                `${magenta('Hook')} - ` +
                                `${isPost}An error happened when running the hook defined in ${underline(extension)} ` +
                                `named ${underline(name)} with action from ${underline(actionExtensionName)} in the ` +
                                `${step} function.`,
                                error
                            );
                        }

                        throw error;
                    }
                };

                if (action.post) {
                    postActions.unshift(initAction(action.post));
                }

                initAction(action.action)();
            }
        });
    });

    postActions.forEach((runAction) => runAction(true));

    return previousValue;
}