libflitter/middleware/MiddlewareUnit.js

/**
 * @module libflitter/middleware/MiddlewareUnit
 */

const CanonicalUnit = require('../canon/CanonicalUnit')
const Middleware = require('./Middleware')
const SoftError = require('../errors/SoftError')
const path = require('path')
const error_context = require('../errors/error_context.fn')

/**
 * Unit to load and manage middleware class definitions.
 * @extends module:libflitter/canon/CanonicalUnit~CanonicalUnit
 */
class MiddlewareUnit extends CanonicalUnit {
    /**
     * Defines the services required by this unit.
     * @returns {Array<string>}
     */
    static get services() {
        return [...super.services, 'output']
    }

    /**
     * Get the name of the service provided by this unit: 'middlewares'
     * @returns {string} - 'middlewares'
     */
    static get name() {
        return 'middlewares'
    }

    /**
     * Instantiate the unit.
     * @param {string} [base_directory = './app/routing/middleware']
     * @param {string} [globals_file = './app/routing/Middleware.js']
     */
    constructor(base_directory = './app/routing/middleware', globals_file = './app/routing/Middleware.js') {
        super(base_directory)

        /**
         * Fully-qualified path to the file with the definitions for globally-applied middleware.
         * @type {Promise<void> | Promise<string>}
         */
        this.globals_file = path.resolve(globals_file)

        /**
         * The canonical name of the item.
         * @type {string} = 'middleware'
         */
        this.canonical_item = 'middleware'

        /**
         * The file extension of the canonical item files.
         * @type {string} - '.middleware.js'
         */
        this.suffix = '.middleware.js'
    }

    /**
     * Initializes the unit. Registers global middleware.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
     * @returns {Promise<void>}
     */
    async go(app) {
        await super.go(app)

        // Register global middlewares
        const globals = require(this.globals_file)
        this.globals = globals

        for ( const name of globals ) {
            if ( !(name in this.canonical_items) ) {
                throw (new SoftError(`Unknown middleware name in global Middleware.js file: ${name}`)).unit(this.constructor.name)
            }

            app.express.use(async (req, res, next, args = {}) => {
                return await this.canonical_items[name].test(req, res, next, args)
            })
        }
    }

    /**
     * Prepare a single canonical middleware definition and return the value that should be given by the resolver.
     * @param {object} info
     * @param {module:libflitter/app/FlitterApp} info.app
     * @param {string} info.name - the unqualified canonical name
     * @param {*} info.instance - the static middleware CLASS from the file
     * @returns {Promise<*>}
     */
    async init_canonical_file({app, name, instance}) {
        if ( instance.prototype instanceof Middleware ) {
            this.output.debug('Registering middleware: '+name)
            return new instance(app)
        } else {
            throw (new SoftError(`Invalid middleware class definition for ${name}. Middleware classes must extend libflitter/middleware/Middleware.`)).unit(this.constructor.name)
        }
    }

    /**
     * A helper function to return the Express middleware function for a registered middleware, using its Flitter canonical name.
     * @param {string} name - the Flitter canonical name of the middleware whose handler should be returned
     * @param {*} args - An argument or arguments to be passed to the middleware function as the 4th argument.
     * @returns {function} - the Express middleware
     */
    get(name, args) {
        try {
            if (!this.canonical_items[name]) {
                throw (new SoftError('Attempted to access middleware that does not exist: ' + name)).unit(this.constructor.name)
            }

            return (req, res, next, merge_args) => {
                let pass_args = typeof args === 'undefined' ? (merge_args ? merge_args : {}) : args
                return this.canonical_items[name].test(req, res, next, pass_args)
            }
        } catch(e) {
            throw error_context(e, {
                resource_name: name,
            })
        }
    }

    /**
     * Get the directories provided by this unit.
     * {@link module:libflitter/middleware/MiddlewareUnit~MiddlewareUnit#directory} as "middleware".
     * @returns {{middleware: string}}
     */
    directories() {
        return {
            ...super.directories(),
            global_middleware: this.globals_file,
        }
    }

    /**
     * Get the templates provided by this unit.
     * Currently, "middleware" template, using the generator {@link module:libflitter/templates/middleware}.
     * @returns {{middleware: {template: (middleware|(function(string): string)|*), extension: string, directory: string}}}
     */
    templates(){
        return {
            middleware: {
                template: require('../templates/middleware'),
                directory: this.directory,
                extension: '.middleware.js'
            }
        }
    }

    /**
     * Get the fully-qualified path to the migrations provided by this unit.
     * @returns {string}
     */
    migrations(){
        return path.resolve(__dirname, 'migrations')
    }
}

module.exports = exports = MiddlewareUnit