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
JavaScript
/* eslint no-param-reassign: off */
;
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)
};