rest-in-contract
Version:
Rest API Contract. This module is the Contract Server nodejs module for `rest-in-contract` project.
263 lines (222 loc) • 9.37 kB
JavaScript
const {
wirestub
} = require('./../models/models');
const { Result } = require('stateful-result').models;
const dsl = require('rest-in-contract-dsl');
const { Middleware } = dsl.baseTypes;
const { recurrsiveEvaluate, recurrsiveCompare, recurrsiveMock, value, stubValue, testValue } = dsl.functions;
const appServices = require('./app-services');
const contractServices = require('./contract-services');
const tcpPortUsed = require('tcp-port-used');
const express = require('express');
const bodyParser = require('body-parser');
const httpShutdown = require('http-shutdown');
const stores = require('./../stores/stores');
function wirestubsStore() {
return stores.getStore('wirestubs');
}
function stubServersStore() {
return stores.getStore('stubServers');
}
const wirestubServices = {
create: async (appId, wirestub) => {
let [error, existingWirestub] = (await wirestubsStore().get(appId)).get();
if (!existingWirestub) {
let [error] = (await startWirestubServer(appId, wirestub)).get();
if (!error) {
let [error] = (await wirestubsStore().create(appId, wirestub)).get();
return Result.newSuccess({ code: 201, message: 'Wirestub created' });
}
} else {
return Result.newSuccess({ code: 200, message: 'Wirestub already running' });
}
},
get: async (appId) => {
let [error, wirestub] = (await wirestubsStore().get(appId)).get();
if (wirestub) {
return Result.newSuccess({ code: 200, data: wirestub });
} else {
return Result.newFail({ code: 404, message: 'Wirestub not found' });
}
},
delete: async (appId) => {
let [error, wirestub] = (await wirestubsStore().get(appId)).get();
if (wirestub) {
let [error] = (await shutdownWirestubServer(appId)).get();
[error] = (await wirestubsStore().delete(appId)).get();
return Result.newSuccess({ code: 204 });
} else {
return Result.newFail({ code: 404, message: 'Wirestub not found' });
}
},
shouldContractHandle: (contract, basePath, req) => {
let self = contract;
let isMatch = true;
let evaluateContext = {
req: {
method: req.method,
urlPath: req.path,
query: req.query,
body: req.body,
rawBody: req.rawBody,
jsonBody: req.jsonBody,
headers: req.headers
},
isStub: true
}
if (self.request.method) {
if (typeof self.request.method === 'string') {
isMatch = isMatch && req.method === self.request.method;
} else if (Array.isArray(self.request.method)) {
isMatch = isMatch && self.request.method.includes(req.method);
}
}
if (self.request.urlPath) {
let incomeReqPath = req.originalUrl.indexOf(basePath) === 0 ? req.path.replace(basePath, '') : req.path;
incomeReqPath.indexOf('/') !== 0 && (incomeReqPath = '/' + incomeReqPath);
let urlPath = self.request.urlPath;
urlPath = recurrsiveEvaluate(urlPath).evaluate(evaluateContext);
isMatch = isMatch && recurrsiveCompare(urlPath).compareFunc(incomeReqPath);
}
if (self.request.queryParameters) {
for (let i = 0; i < self.request.queryParameters.length; i++) {
let queryParam = self.request.queryParameters[i];
let paramName = queryParam.name;
paramName = recurrsiveEvaluate(paramName).evaluate(evaluateContext);
let paramValue = queryParam.value;
paramValue = recurrsiveEvaluate(paramValue).evaluate(evaluateContext);
isMatch = isMatch && recurrsiveCompare(paramValue).compareFunc(req.query[paramName]);
}
}
if (self.request.headers) {
for (let headerKey in self.request.headers) {
let headerValue = self.request.headers[headerKey];
headerValue = recurrsiveEvaluate(headerValue).evaluate(evaluateContext);
isMatch = isMatch && recurrsiveCompare(headerValue).compareFunc(req.headers[headerKey.toLowerCase()]);
}
}
if (self.request.body) {
let body = self.request.body;
body = recurrsiveEvaluate(body).evaluate(evaluateContext);
isMatch = isMatch && recurrsiveCompare(body).compareFunc(req.body);
}
return isMatch;
},
doContractHandle: (contract, basePath, req, res) => {
let self = contract;
let incomeReqPath = req.path.indexOf(basePath) === 0 ? req.path.replace(basePath, '') : req.path;
let evaluateContext = {
req: {
method: req.method,
basePath: basePath,
path: incomeReqPath,
query: req.query,
body: req.body,
rawBody: req.rawBody,
jsonBody: req.jsonBody,
headers: req.headers
}
}
for (let headerKey in self.response.headers) {
let headerValue = self.response.headers[headerKey];
headerValue = recurrsiveEvaluate(headerValue).evaluate(evaluateContext);
headerValue = recurrsiveMock(headerValue).mock();
res.set(headerKey, headerValue);
}
let responseStatus = self.response.status;
if (Middleware.isMiddleware(responseStatus)) {
responseStatus = recurrsiveEvaluate(responseStatus).evaluate(evaluateContext);
responseStatus = recurrsiveMock(responseStatus).mock();
}
let responseBody = self.response.body;
if (typeof responseBody === 'object') {
responseBody = recurrsiveEvaluate(responseBody).evaluate(evaluateContext);
responseBody = recurrsiveMock(responseBody).mock();
}
res.status(responseStatus).send(responseBody);
return true;
}
}
async function startWirestubServer(appId, wirestub) {
let isPortFree = !(await tcpPortUsed.check(wirestub.port));
if (isPortFree) {
let appInstance = express();
appInstance.use((req, res, next) => {
req.rawBody = "";
var data = '';
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
req.rawBody = data;
});
next();
});
appInstance.use(bodyParser.json({
limit: '50mb'
}));
appInstance.use(bodyParser.urlencoded({
extended: false,
limit: '50mb'
}));
appInstance.use(bodyParser.raw({
type: 'application/vnd.js',
limit: '50mb'
}));
appInstance.use((req, res, next) => {
req.jsonBody = {};
if (req.body && typeof req.body === 'object') {
req.jsonBody = req.body;
}
next();
});
appInstance.appId = appId;
appInstance.all('*', async function (req, res) {
let appId = appInstance.appId;
let [error, app] = (await appServices.get(appId)).get();
let appBasePath = app.basePath;
for (let version of app.versions) {
let v = version.v;
let versionedPath = version.path;
let contractIds = version.contracts;
let basePath = appBasePath;
if (versionedPath) {
basePath = versionedPath.replace(/\{\{app\.basePath}}/g, appBasePath).replace(/\{\{version\.v}}/g, v);
}
if (req.originalUrl.indexOf(basePath) !== 0) {
continue;
}
for (let contractId of contractIds) {
let [error, contract] = (await contractServices.get(contractId)).get();
if (contract && wirestubServices.shouldContractHandle(contract, basePath, req)) {
return wirestubServices.doContractHandle(contract, basePath, req, res);
}
}
}
return res.status(404).send('Not found');
});
let serverInstance = appInstance.listen(wirestub.port);
serverInstance = httpShutdown(serverInstance);
await stubServersStore().set(appId, serverInstance);
return Result.newSuccess({ code: 201, message: 'Stub server created' });
} else {
return Result.newFail({ code: 500, message: 'Internal server error' });
}
}
async function shutdownWirestubServer(appId) {
let [error, serverInstance] = (await stubServersStore().get(appId)).get();
await new Promise((resolve, reject) => {
serverInstance.forceShutdown(async () => {
console.log('Wirestub server shutdown.');
let [error] = (await stubServersStore().delete(appId)).get();
if (!error) {
resolve();
} else {
reject(error);
}
});
});
return Result.newSuccess({ code: 204 });
}
module.exports = wirestubServices;
;