UNPKG

ts-claudia-local-api

Version:

Command line utility to launch Express local API for claudia-api-builder. Test drive your lambda functions before deployment (BASED on Sudharshan Ravindran claudia-local-api )

219 lines (184 loc) 6.82 kB
/* eslint no-param-reassign: off */ 'use strict'; const program = require('commander'); const path = require('path'); const pathParser = require('path-parser'); const packageJson = require('../package'); const { v4: uuidv4 } = require('uuid'); function getDefaultConfig() { return { port: 3000, lambdaVersion: 'develop' }; } function initServer() { const express = require('express'); const bodyParser = require('body-parser'); const server = express(); server.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); server.use(bodyParser.json({ limit: '10mb' })); server.use(function (req, res, next) { req.headers['if-none-match'] = 'no-match-for-this'; next(); }); return server; } function initLogger(abbreviation) { const bunyan = require('bunyan'); const logger = bunyan.createLogger({ name: packageJson.name }); // initialize maximum body logging length logger.bodyAbbreviationLength = abbreviation; return logger; } function logJson(logger, body) { // If the body is too long of a string, abbreviate it if (body.body && body.body.length && body.body.length > logger.bodyAbbreviationLength) { // Make a copy so the original body doesn't get mutated const content = Object.assign({}, body); /* eslint-disable-next-line no-param-reassign */ content.body = `${content.body.substr(0, logger.bodyAbbreviationLength - 3)}...`; logger.info(JSON.stringify(content, null, 4)); } else { logger.info(JSON.stringify(body, null, 4)); } } function logError(logger, error) { logger.error(error.message); logger.error(error.stack); } function getPathParams(req, routes) { const parsedPath = req._parsedUrl.pathname; for (const route of routes) { const isSupported = route.supportedMethods.indexOf(req.method) !== -1; const pathParameters = route.path.test(parsedPath); if (isSupported && pathParameters) { return { resourcePath: route.resourcePath, pathParameters }; } } return { resourcePath: parsedPath, pathParameters: {} }; } function getParams(req, routes, lambdaVersion = null) { const uuid = uuidv4(); const pathParams = getPathParams(req, routes); let paramsObject = { requestContext: { resourcePath: pathParams.resourcePath, httpMethod: req.method, resourceId: uuid }, headers: req.headers, queryStringParameters: req.query, body: req.body, pathParameters: pathParams.pathParameters }; if(lambdaVersion){ paramsObject['stageVariables'] = { lambdaVersion: lambdaVersion } } return paramsObject; } // Determine whether to enable binary content handling function parseContentHandling(params, app) { const requestContext = params.requestContext; // path in API Config is indexed without the leading slash const apiPath = requestContext.resourcePath.slice(1); const httpMethod = requestContext.httpMethod; if (app.apiConfig().routes && app.apiConfig().routes[apiPath] && app.apiConfig().routes[apiPath][httpMethod] && app.apiConfig().routes[apiPath][httpMethod].success) { return app.apiConfig().routes[apiPath][httpMethod].success.contentHandling; } return false; } function makeHandleResponse(logger, res, convertToBinary) { return function (err, response) { if (err) { logError(logger, err); const body = { message: err.message }; return res .status(500) .send(body); } logJson(logger, response); // If the CONVERT_TO_BINARY flag was set, return the response as a buffer if (convertToBinary === 'CONVERT_TO_BINARY') { return res .set(response.headers || {}) .status(response.statusCode || 200) .send(response.body ? Buffer.from(response.body, 'base64') : undefined); } return res .set(response.headers || {}) .status(response.statusCode || 200) .send(response.body || {}); }; } function makeHandleRequest(logger, app, routes, lambdaVersion) { return function (req, res) { const params = getParams(req, routes, lambdaVersion); logJson(logger, params); const convertToBinary = parseContentHandling(params, app); app.proxyRouter(params, { done: makeHandleResponse(logger, res, convertToBinary) }); }; } function getRoutes(routesObj) { const routePaths = Object.keys(routesObj); return routePaths.map(function (routePath) { let supportedMethods = Object.keys(routesObj[routePath] || {}); if(supportedMethods == 'ANY'){ supportedMethods = `GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS`; } const route = `/${routePath}`; return { resourcePath: route, supportedMethods, path: pathParser.Path.createPath(route.replace(/{(.+?)}/g, ':$1')) }; }); } function bootstrap(server, logger, claudiaApp, routes, options) { const handleRequest = makeHandleRequest(logger, claudiaApp, routes, options.stage); server.all('*', handleRequest); const instance = server.listen(options.port); logger.info(`Server listening on ${options.port}`); return instance; } function runCmd(bootstrapFn) { const config = getDefaultConfig(); program .version(packageJson.version) .option('-a --api-module <apiModule>', 'Specify claudia api path from project root') .option('-p --port [port]', `Specify port to use [${config.port}]`, config.port) .option('--abbrev [body length]', 'Specify the maximum logged length of a response/request body [no abbreviation]', -1) .option('--stage', 'Specify the AWS lambda stage [develop, latest]', 'develop') .parse(process.argv); const apiPath = path.join(process.cwd(), program.apiModule); const claudiaApp = require(apiPath); const apiConfig = claudiaApp.apiConfig(); const routes = getRoutes(apiConfig.routes); const server = initServer(); const logger = initLogger(program.abbrev); bootstrapFn(server, logger, claudiaApp, routes, program); } module.exports = { run: runCmd.bind(null, bootstrap) };