Skip to content

Commit

Permalink
refactor: refactor the framework router component: add convenience me…
Browse files Browse the repository at this point in the history
…thods for creating routes
  • Loading branch information
simplymichael committed Jun 21, 2024
1 parent ca5c72f commit 2d317d3
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/framework/component/router/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const Router = require("./router");
const { createRequestHandler } = require("./routing-functions");


module.exports = Router;
module.exports.createRequestHandler = createRequestHandler;
File renamed without changes.
151 changes: 151 additions & 0 deletions src/framework/component/router/routing-functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const fs = require("node:fs");
const path = require("node:path");
const express = require("express");
const router = {};


module.exports = {
createRouter,
createRoutes,
createRequestHandler,
};

/**
* Create router from route definitions in a given directory.
*
* @param {String} routeDefinitionsPath: The directory containing route definitions
* @param {Object} container (optional): A Dependency Injection container.
* If supplied, the container is used to resolve controllers.
* In that case, ensure the containers are registered to the container.
* If the container is not supplied, ensure the controllers are in scope.
*/
function createRouter(routeDefinitionsPath, container) {
const routesPath = routeDefinitionsPath;
const routeFiles = fs.readdirSync(routesPath);

for(let i = 0, len = routeFiles.length; i < len; i++) {
const filename = path.basename(routeFiles[i], ".js");
const routeDefinitions = require(`${routesPath}/${filename}`);

router[filename] = createRoutes(routeDefinitions, container);
}

return router;
};

/**
* Create router from given route definitions
*
* @param {Object} routeDefinitions: The route definitions object.
* The object will contain route definition objects.
* Each route definition object will have the following members:
* - {String} method: The request method
* - {String} path: The request path
* - {Array} parameters (optional): List of parameter variables in the route
* - {Array} middleware (optional): Ordered list of middleware to be applied to the router
* - {Array|Function|Object|String} handler: The request handler for the route.
* - if Array, it should be in the format: [controller: Class, handler: function]
* - if Function, it should be an inline function or the function reference
* - if Object, it should be in the format: { controller: controllerRef, method: function}
* - if String, it should be in the format: "controller.method"
* - {String} description (optional): A description of the route's purpose.
* @param {Object} container (optional): A Dependency Injection container.
* If supplied, the container is used to resolve controllers.
* In that case, ensure the containers are registered to the container.
* If the container is not supplied, ensure the controllers are in scope.
* @return {express.Router}
*/
function createRoutes(routeDefinitions, container) {
const router = express.Router();

Object.values(routeDefinitions).forEach((rd) => {
const { method, path, middleware, handler } = rd;
const requestHandler = createRequestHandler(handler, container);

router[method.toLowerCase()](path, ...middleware, requestHandler);
});

return router;
}

/**
* Create a request handler
*
* @param {Array|Function|Object|String} handler: The request handler for the route.
* - if Array, it should be in the format: [controller: Class, handler: function]
* - if Function, it should be an inline function or the function reference
* - if Object, it should be in the format: { controller: controllerRef, method: function}
* - if String, it should be in the format: "controller.method"
* @param {Object} container (optional): A Dependency Injection container.
* If supplied, the container is used to resolve controllers.
* In that case, ensure the containers are registered to the container.
* If the container is not supplied, ensure the controllers are in scope.
*/
function createRequestHandler(handler, container) {
let requestHandler;

if(typeof handler === "function") {
requestHandler = handler;
} else if(Array.isArray(handler) && handler.length === 2) {
const [controller, method] = handler;

requestHandler = resolveRequestHandler(controller, method, container);
} else if(handler && typeof handler === "object" && "controller" in handler && "method" in handler) {
const { controller, method } = handler;

requestHandler = resolveRequestHandler(controller, method, container);
} else if(typeof handler === "string") {
const [controller, method] = handler.split(".").map(s => s.trim());

requestHandler = resolveRequestHandler(controller, method, container);
} else {
throw new TypeError(
`Invalid handler ${handler} specified for ${method} ${path}.
The handler should be a function, an array of the form [controller: string, method: string]
or an object with two properties: controller: string, method: string.
`
);
}

return requestHandler;
}

/**
* Resolve a controller handler (from the container or via require)
* @param {String|Object} controllerRef: A controller name string or a reference to a controller object.
* If this is a string, the corresponding name must have been registered in the DI Container.
* @param {String|Object} methodRef: A method name string or a reference to a callable.
* The method must exist as a member of the controller.
* @returns {Callable}
*/
function resolveRequestHandler(controllerRef, methodRef, container) {
let controller;
let methodName;

if(container && typeof container.resolve === "function") {
if(typeof controllerRef === "string") {
controller = container.resolve(controllerRef);
} else {
controller = controllerRef;
}
} else {
controller = require(controllerRef);
}


if(!controller) {
throw new TypeError(`Controller ${controllerRef} not found`);
}

if(typeof methodRef === "string" && (methodRef in controller)) {
methodName = methodRef;
} else if(methodRef.name in controller) {
methodName = methodRef.name;
}

if(!methodName) {
throw new TypeError(`Method ${String(methodRef)} not found in controller ${controllerRef}`);
}

return controller[methodName].bind(controller);
}

0 comments on commit 2d317d3

Please sign in to comment.