Skip to content

Commit e505d19

Browse files
author
diana.ionita
committed
Merge branch 'release/1.2.0'
2 parents 45d808e + 07ded7b commit e505d19

10 files changed

+214
-66
lines changed

package-lock.json

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-api-gateway-caching",
3-
"version": "1.1.6",
3+
"version": "1.2.0",
44
"description": "A plugin for the serverless framework which helps with configuring caching for API Gateway endpoints.",
55
"main": "src/apiGatewayCachingPlugin.js",
66
"scripts": {
@@ -19,7 +19,8 @@
1919
"license": "ISC",
2020
"dependencies": {
2121
"lodash.get": "^4.4.2",
22-
"lodash.isempty": "^4.4.0"
22+
"lodash.isempty": "^4.4.0",
23+
"lodash.split": "^4.4.0"
2324
},
2425
"bugs": {
2526
"url": "https://github.com/DianaIonita/serverless-api-gateway-caching/issues"

src/ApiGatewayCachingSettings.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ const mapUnauthorizedRequestStrategy = strategy => {
1818
}
1919
}
2020

21-
const isApiGatewayEndpoint = functionSettings => {
22-
if (isEmpty(functionSettings.events)) {
23-
return false;
24-
}
25-
return functionSettings.events.filter(e => e.http != null).length > 0;
21+
const isApiGatewayEndpoint = event => {
22+
return event.http ? true: false;
2623
}
2724

2825
class PerKeyInvalidationSettings {
@@ -43,15 +40,18 @@ class PerKeyInvalidationSettings {
4340
}
4441

4542
class ApiGatewayEndpointCachingSettings {
46-
constructor(functionName, functionSettings, globalSettings) {
43+
constructor(customFunctionName, functionName, event, globalSettings) {
44+
this.customFunctionName = customFunctionName;
4745
this.functionName = functionName;
48-
49-
// TODO multiple http endpoints
50-
let cachingConfig = functionSettings.events.filter(e => e.http != null)[0].http.caching;
51-
if (!cachingConfig) {
46+
47+
this.path = event.http.path;
48+
this.method = event.http.method;
49+
50+
if (!event.http.caching) {
5251
this.cachingEnabled = false;
5352
return;
5453
}
54+
let cachingConfig = event.http.caching;
5555
this.cachingEnabled = globalSettings.cachingEnabled ? cachingConfig.enabled : false;
5656
this.cacheTtlInSeconds = cachingConfig.ttlInSeconds || globalSettings.cacheTtlInSeconds;
5757
this.cacheKeyParameters = cachingConfig.cacheKeyParameters;
@@ -88,8 +88,10 @@ class ApiGatewayCachingSettings {
8888

8989
for (let functionName in serverless.service.functions) {
9090
let functionSettings = serverless.service.functions[functionName];
91-
if (isApiGatewayEndpoint(functionSettings)) {
92-
this.endpointSettings.push(new ApiGatewayEndpointCachingSettings(functionName, functionSettings, this))
91+
for(let event in functionSettings.events) {
92+
if (isApiGatewayEndpoint(functionSettings.events[event])) {
93+
this.endpointSettings.push(new ApiGatewayEndpointCachingSettings(functionSettings.name, functionName, functionSettings.events[event], this))
94+
}
9395
}
9496
}
9597
}

src/apiGatewayCachingPlugin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const ApiGatewayCachingSettings = require('./ApiGatewayCachingSettings');
4-
const addPathParametersCacheConfig = require('./pathParametersCache');
4+
const pathParametersCache = require('./pathParametersCache');
55
const updateStageCacheSettings = require('./stageCache');
66
const { restApiExists, outputRestApiIdTo } = require('./restApiId');
77

@@ -35,7 +35,7 @@ class ApiGatewayCachingPlugin {
3535
return;
3636
}
3737

38-
return addPathParametersCacheConfig(this.settings, this.serverless);
38+
return pathParametersCache.addPathParametersCacheConfig(this.settings, this.serverless);
3939
}
4040

4141
updateStage() {

src/pathParametersCache.js

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,65 @@
1-
const isEmpty = require('lodash.isempty');
1+
const split = require('lodash.split');
22

3-
const getResourcesByType = (type, serverless) => {
4-
let result = []
3+
const getResourcesByName = (name, serverless) => {
54
let resourceKeys = Object.keys(serverless.service.provider.compiledCloudFormationTemplate.Resources);
65
for (let resourceName of resourceKeys) {
7-
let resource = serverless.service.provider.compiledCloudFormationTemplate.Resources[resourceName];
8-
if (resource.Type == type) {
9-
result.push({ name: resourceName, resource });
6+
if (resourceName == name) {
7+
return serverless.service.provider.compiledCloudFormationTemplate.Resources[resourceName];
108
}
119
}
12-
return result;
1310
}
1411

15-
const getResourceForLambdaFunctionNamed = (fullFunctionName, serverless) => {
16-
let lambdaResource = getResourcesByType('AWS::Lambda::Function', serverless).filter(r => r.resource.Properties.FunctionName == fullFunctionName);
17-
if (isEmpty(lambdaResource)) {
18-
throw new Error('Something has gone wrong');
19-
}
20-
return lambdaResource[0];
21-
}
22-
23-
const getApiGatewayMethodFor = (functionName, stage, serverless) => {
24-
const fullFunctionName = `${serverless.service.service}-${stage}-${functionName}`;
25-
const lambdaFunctionResource = getResourceForLambdaFunctionNamed(fullFunctionName, serverless);
12+
const getApiGatewayMethodNameFor = (path, httpMethod) => {
13+
const pathElements = split(path,'/');
14+
pathElements.push(httpMethod.toLowerCase());
15+
let gatewayResourceName = pathElements
16+
.map (element => {
17+
element = element.toLowerCase();
18+
if(element.startsWith('{')) {
19+
element = element.substring(element.indexOf('{') + 1,element.indexOf('}')) + "Var";
20+
}
21+
//capitalize first letter
22+
return element.charAt(0).toUpperCase() + element.slice(1);
23+
}).reduce((a, b) => a + b);
2624

27-
// returns the first method found which depends on this lambda
28-
const methods = getResourcesByType('AWS::ApiGateway::Method', serverless);
29-
for (let method of methods) {
30-
let stringified = JSON.stringify(method);
31-
if (stringified.lastIndexOf(`"${lambdaFunctionResource.name}"`) != -1) {
32-
return method;
33-
}
34-
}
25+
gatewayResourceName = "ApiGatewayMethod" + gatewayResourceName;
26+
return gatewayResourceName;
3527
}
3628

3729
const addPathParametersCacheConfig = (settings, serverless) => {
3830
for (let endpointSettings of settings.endpointSettings) {
3931
if (!endpointSettings.cacheKeyParameters) {
4032
continue;
33+
}
34+
const resourceName = getApiGatewayMethodNameFor(endpointSettings.path, endpointSettings.method);
35+
const method = getResourcesByName(resourceName,serverless);
36+
if (!method) {
37+
serverless.cli.log(`[serverless-api-gateway-caching] The method ${resourceName} couldn't be found in the
38+
compiled CloudFormation template. Caching settings will not be updated for this endpoint.`);
39+
const index = settings.endpointSettings.indexOf(endpointSettings);
40+
if(index != -1) {
41+
settings.endpointSettings.splice(index,1);
42+
}
43+
return;
4144
}
42-
const method = getApiGatewayMethodFor(endpointSettings.functionName, settings.stage, serverless);
43-
if (!method.resource.Properties.Integration.CacheKeyParameters) {
44-
method.resource.Properties.Integration.CacheKeyParameters = [];
45+
if (!method.Properties.Integration.CacheKeyParameters) {
46+
method.Properties.Integration.CacheKeyParameters = [];
4547
}
46-
if (!method.resource.Properties.Integration.RequestParameters) {
47-
method.resource.Properties.Integration.RequestParameters = {}
48+
if (!method.Properties.Integration.RequestParameters) {
49+
method.Properties.Integration.RequestParameters = {}
4850
}
4951

5052
for (let cacheKeyParameter of endpointSettings.cacheKeyParameters) {
51-
let existingValue = method.resource.Properties.RequestParameters[`method.${cacheKeyParameter.name}`];
52-
method.resource.Properties.RequestParameters[`method.${cacheKeyParameter.name}`] = (existingValue == null || existingValue == undefined) ? {} : existingValue;
53-
method.resource.Properties.Integration.RequestParameters[`integration.${cacheKeyParameter.name}`] = `method.${cacheKeyParameter.name}`;
54-
method.resource.Properties.Integration.CacheKeyParameters.push(`method.${cacheKeyParameter.name}`);
53+
let existingValue = method.Properties.RequestParameters[`method.${cacheKeyParameter.name}`];
54+
method.Properties.RequestParameters[`method.${cacheKeyParameter.name}`] = (existingValue == null || existingValue == undefined) ? {} : existingValue;
55+
method.Properties.Integration.RequestParameters[`integration.${cacheKeyParameter.name}`] = `method.${cacheKeyParameter.name}`;
56+
method.Properties.Integration.CacheKeyParameters.push(`method.${cacheKeyParameter.name}`);
5557
}
56-
method.resource.Properties.Integration.CacheNamespace = `${method.name}CacheNS`;
58+
method.Properties.Integration.CacheNamespace = `${resourceName}CacheNS`;
5759
}
5860
}
5961

60-
module.exports = addPathParametersCacheConfig;
62+
module.exports = {
63+
addPathParametersCacheConfig: addPathParametersCacheConfig,
64+
getApiGatewayMethodNameFor: getApiGatewayMethodNameFor
65+
}

src/stageCache.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,22 @@ const patchForMethod = (path, method, endpointSettings) => {
6464
return patch;
6565
}
6666

67+
const httpEventOf = (lambda, endpointSettings) => {
68+
return lambda.events.filter(e => e.http != undefined)
69+
.filter(e => e.http.path === endpointSettings.path || "/" + e.http.path === endpointSettings.path)
70+
.filter(e => e.http.method === endpointSettings.method);
71+
}
72+
6773
const createPatchForEndpoint = (endpointSettings, serverless) => {
6874
let lambda = serverless.service.getFunction(endpointSettings.functionName);
6975
if (isEmpty(lambda.events)) {
7076
serverless.cli.log(`[serverless-api-gateway-caching] Lambda ${endpointSettings.functionName} has not defined events.`);
77+
return;
7178
}
72-
// TODO there can be many http events
73-
let httpEvents = lambda.events.filter(e => e.http != null);
79+
const httpEvents = httpEventOf(lambda,endpointSettings);
7480
if (isEmpty(httpEvents)) {
7581
serverless.cli.log(`[serverless-api-gateway-caching] Lambda ${endpointSettings.functionName} has not defined any HTTP events.`);
82+
return;
7683
}
7784
let { path, method } = httpEvents[0].http;
7885

test/configuring-path-parameters.js

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const APP_ROOT = '..';
22
const given = require(`${APP_ROOT}/test/steps/given`);
33
const ApiGatewayCachingSettings = require(`${APP_ROOT}/src/ApiGatewayCachingSettings`);
4-
const addPathParametersCacheConfig = require(`${APP_ROOT}/src/pathParametersCache`);
4+
const pathParams = require(`${APP_ROOT}/src/pathParametersCache`);
55
const expect = require('chai').expect;
66

77
describe('Configuring path parameter caching', () => {
@@ -267,8 +267,99 @@ describe('Configuring path parameter caching', () => {
267267
});
268268
});
269269
});
270+
describe('when there are two endpoints with a cache key parameter on the same function', () => {
271+
let method, functionName, firstEndpointCacheKeyParameters, secondEndpointCacheKeyParameters;
272+
before(() => {
273+
functionName = 'catpaw';
274+
275+
firstEndpointCacheKeyParameters = [{ name: 'request.path.pawId' }];
276+
secondEndpointCacheKeyParameters = [{ name: 'request.path.pawId' }];
277+
278+
let firstFunctionWithCaching = given.a_serverless_function(functionName)
279+
.withHttpEndpoint('get', '/cat/paw/{pawId}', { enabled: true, cacheKeyParameters: firstEndpointCacheKeyParameters })
280+
.withHttpEndpoint('delete', '/cat/paw/{pawId}', { enabled: true, cacheKeyParameters: secondEndpointCacheKeyParameters });
281+
serverless = given.a_serverless_instance(serviceName)
282+
.withApiGatewayCachingConfig(true, '0.5', 45)
283+
.forStage(stage)
284+
.withFunction(firstFunctionWithCaching)
285+
286+
cacheSettings = new ApiGatewayCachingSettings(serverless);
287+
288+
when_configuring_path_parameters(cacheSettings, serverless);
289+
});
290+
291+
describe('on the method corresponding with the first endpoint with cache key parameters', () => {
292+
before(() => {
293+
method = serverless.getMethodResourceForMethodName("ApiGatewayMethodCatPawPawidVarGet");
294+
});
295+
296+
it('should configure them as request parameters', () => {
297+
for (let parameter of firstEndpointCacheKeyParameters) {
298+
expect(method.Properties.RequestParameters)
299+
.to.deep.include({
300+
[`method.${parameter.name}`]: {}
301+
});
302+
}
303+
});
304+
305+
it('should set integration request parameters', () => {
306+
for (let parameter of firstEndpointCacheKeyParameters) {
307+
expect(method.Properties.Integration.RequestParameters)
308+
.to.deep.include({
309+
[`integration.${parameter.name}`]: `method.${parameter.name}`
310+
});
311+
}
312+
});
313+
314+
it('should set integration cache key parameters', () => {
315+
for (let parameter of firstEndpointCacheKeyParameters) {
316+
expect(method.Properties.Integration.CacheKeyParameters)
317+
.to.include(`method.${parameter.name}`);
318+
}
319+
});
320+
321+
it('should set a cache namespace', () => {
322+
expect(method.Properties.Integration.CacheNamespace).to.exist;
323+
});
324+
});
325+
326+
describe('on the method corresponding with the second endpoint with cache key parameters', () => {
327+
before(() => {
328+
method = serverless.getMethodResourceForMethodName("ApiGatewayMethodCatPawPawidVarDelete");
329+
});
330+
331+
it('should configure them as request parameters', () => {
332+
for (let parameter of secondEndpointCacheKeyParameters) {
333+
expect(method.Properties.RequestParameters)
334+
.to.deep.include({
335+
[`method.${parameter.name}`]: {}
336+
});
337+
}
338+
});
339+
340+
it('should set integration request parameters', () => {
341+
for (let parameter of secondEndpointCacheKeyParameters) {
342+
expect(method.Properties.Integration.RequestParameters)
343+
.to.deep.include({
344+
[`integration.${parameter.name}`]: `method.${parameter.name}`
345+
});
346+
}
347+
});
348+
349+
it('should set integration cache key parameters', () => {
350+
for (let parameter of secondEndpointCacheKeyParameters) {
351+
expect(method.Properties.Integration.CacheKeyParameters)
352+
.to.include(`method.${parameter.name}`);
353+
}
354+
});
355+
356+
it('should set a cache namespace', () => {
357+
expect(method.Properties.Integration.CacheNamespace).to.exist;
358+
});
359+
});
360+
});
270361
});
271362

272363
const when_configuring_path_parameters = (settings, serverless) => {
273-
return addPathParametersCacheConfig(settings, serverless);
364+
return pathParams.addPathParametersCacheConfig(settings, serverless);
274365
}

test/creating-settings.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ describe('Creating settings', () => {
223223

224224
let getCatByPawIdCaching = { enabled: true, ttlInSeconds: 30 };
225225
let getCatByPawId = given.a_serverless_function(getCatByPawIdFunctionName)
226-
.withHttpEndpoint('get', '/cat/{pawId}', getCatByPawIdCaching);
226+
.withHttpEndpoint('get', '/cat/{pawId}', getCatByPawIdCaching)
227+
.withHttpEndpoint('delete', '/cat/{pawId}', getCatByPawIdCaching);
227228

228229
let getMyCatCaching = { enabled: false };
229230
let getMyCat = given.a_serverless_function(getMyCatFunctionName)
@@ -240,7 +241,7 @@ describe('Creating settings', () => {
240241
});
241242

242243
it('should create cache settings for all http endpoints', () => {
243-
expect(cacheSettings.endpointSettings).to.have.lengthOf(3);
244+
expect(cacheSettings.endpointSettings).to.have.lengthOf(4);
244245
});
245246

246247
describe('caching for http endpoint without cache settings defined', () => {

0 commit comments

Comments
 (0)