serverless-offline-python
Version:
Emulate AWS λ and API Gateway locally when developing your Serverless project
174 lines (135 loc) • 4.72 kB
JavaScript
'use strict';
const reverse = require('lodash/fp/reverse');
const any = require('lodash/fp/any');
// const map = require('lodash/fp/map');
const APIGATEWAY_TYPE_RESOURCE = 'AWS::ApiGateway::Resource';
const APIGATEWAY_TYPE_METHOD = 'AWS::ApiGateway::Method';
const APIGATEWAY_ROOT_ID = 'RootResourceId';
const APIGATEWAY_INTEGRATION_TYPE_HTTP_PROXY = 'HTTP_PROXY';
function getApiGatewayTemplateObjects(resources) {
const Resources = resources && resources.Resources;
if (!Resources) return {};
const pathObjects = {};
const methodObjects = {};
for (const k in Resources) {
const resourceObj = Resources[k] || {};
const Type = resourceObj.Type;
if (Type === APIGATEWAY_TYPE_RESOURCE) {
pathObjects[k] = resourceObj;
}
else if (Type === APIGATEWAY_TYPE_METHOD) {
methodObjects[k] = resourceObj;
}
}
return {
pathObjects,
methodObjects,
};
}
/* Example of a Resource Object
* "ApiGatewayResourceFavicon": {
* "Type": "AWS::ApiGateway::Resource",
* "Properties": {
* "ParentId": { "Fn::GetAtt": [ "ApiGatewayRestApi", "RootResourceId" ] },
* "PathPart": "favicon.ico",
* "RestApiId": { "Ref": "ApiGatewayRestApi" }
* }
* }
*/
/* Resource Helpers */
function isRoot(resourceId) { return resourceId === APIGATEWAY_ROOT_ID; }
function getPathPart(resourceObj) {
if (!resourceObj || !resourceObj.Properties) return;
return resourceObj.Properties.PathPart;
}
function getParentId(resourceObj) {
if (!resourceObj || !resourceObj.Properties) return;
const parentIdObj = resourceObj.Properties.ParentId || {};
const Ref = parentIdObj.Ref;
if (Ref) return Ref;
const getAtt = parentIdObj['Fn::GetAtt'] || [];
return getAtt[1];
}
function getFullPath(pathObjects, resourceId) {
let currentId = resourceId;
let currentObj;
const arrResourceObjects = [];
while (currentId && !isRoot(currentId)) {
currentObj = pathObjects[currentId];
arrResourceObjects.push(currentObj);
currentId = getParentId(currentObj);
}
const arrPath = reverse(arrResourceObjects.map(getPathPart));
if (any(s => !s)(arrPath)) return;
return `/${arrPath.join('/')}`;
}
/* Example of an HTTP Proxy Method Object
* "ApiGatewayResourcePublicAnyProxyMethod": {
* "Type": "AWS::ApiGateway::Method",
* "Properties": {
* "ResourceId": { "Ref": "ApiGatewayResourcePublicAny" },
* "RestApiId": { "Ref": "ApiGatewayRestApi" },
* "AuthorizationType": "NONE",
* "HttpMethod": "GET",
* "MethodResponses": [ { "StatusCode": 200 } ],
* "RequestParameters": { "method.request.path.proxy": true },
* "Integration": {
* "Type": "HTTP_PROXY",
* "IntegrationHttpMethod": "GET",
* "Uri": "https://www.example.com/us-west-2/{proxy}", // Note that ${self:provider.region} is expanded
* "IntegrationResponses": [ { "StatusCode": 200 } ],
* "RequestParameters": { "integration.request.path.proxy": "method.request.path.proxy" },
* "PassthroughBehavior": "WHEN_NO_MATCH"
* }
* }
* }
*/
/* Method Helpers */
function getResourceId(methodObj) {
if (!methodObj || !methodObj.Properties) return;
if (!methodObj.Properties.ResourceId) return;
return methodObj.Properties.ResourceId.Ref;
}
function getHttpMethod(methodObj) {
if (!methodObj || !methodObj.Properties) return;
return methodObj.Properties.HttpMethod;
}
function getIntegrationObj(methodObj) {
if (!methodObj || !methodObj.Properties) return;
return methodObj.Properties.Integration;
}
function templatePathToHapiPath(path) {
return path.replace('+', '');
}
function constructHapiInterface(pathObjects, methodObjects, methodId) {
// returns all info necessary so that routes can be added in index.js
const methodObj = methodObjects[methodId];
const resourceId = getResourceId(methodObj);
const Integration = getIntegrationObj(methodObj) || {};
const pathResource = getFullPath(pathObjects, resourceId);
const method = getHttpMethod(methodObj);
// let integrationType;
let proxyUri;
if (!pathResource) return {};
const path = templatePathToHapiPath(pathResource);
if (Integration.Type === APIGATEWAY_INTEGRATION_TYPE_HTTP_PROXY) {
proxyUri = Integration.Uri;
}
return {
path,
method,
isProxy: !!proxyUri,
proxyUri,
pathResource,
};
}
module.exports = resources => {
const intf = getApiGatewayTemplateObjects(resources);
const pathObjects = intf.pathObjects;
const methodObjects = intf.methodObjects;
const result = {};
for (const methodId in methodObjects) {
result[methodId] = constructHapiInterface(pathObjects, methodObjects, methodId);
}
return result;
};