From c24eacbacd9ceea18729602f70b9e02e7cdc2c5f Mon Sep 17 00:00:00 2001 From: Jan Hrdina Date: Fri, 10 Feb 2023 10:32:42 +0100 Subject: [PATCH] feat: make exclude on array items remove them completely BREAKING CHANGE: Exclusion of an array item now removes it completely instead of keeping undefined on its index. --- __tests__/index.test.js | 55 +++++++++++++++++++++++++++++-- index.js | 72 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/__tests__/index.test.js b/__tests__/index.test.js index eeb2c15..2d04a08 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -10,11 +10,19 @@ describe("Test Serverless IfElse Plugin With Condition Set 1", () => { serverlessIfElse.applyConditions(); }); - it("It Should Remove Serverless Properties in Exlude when If condition Matches", () => { + it("It Should Remove Serverless Properties in Exclude when If condition Matches", () => { expect(serverless.service.functions.func1).toBeUndefined(); expect(serverless.service.functions.role).toBeUndefined(); }); + + + it("It Should Remove Array Items in Exclude when If condition Matches", () => { + expect(serverless.service.custom.cors.headers).toEqual([ + "Normal-Header" + ]); + }); + it("It Should Set Serverless Properties in Set When If condition Matches", () => { expect(serverless.service.provider.profile).toBe("dev"); }); @@ -69,6 +77,7 @@ describe("Test Serverless IfElse Plugin With Condition Set 2", () => { it("It Should Remove Serverless Properties in ExcludeIf when condition Matches", () => { expect(serverless.service.functions.func3).toBeUndefined(); + expect(serverless.service.custom.cors.headers).toEqual(["Normal-Header"]); }); }); @@ -86,6 +95,37 @@ describe("Test Serverless IfElse Plugin With Condition Set 3", () => { }); }); +describe("sortKeyPathsDesc", () => { + let slsIfElse; + beforeAll(() => { + slsIfElse = new serverlessPluginIfElse({}); + }); + + it("It Should Sort Indices as Numbers", () => { + expect(slsIfElse.sortKeyPathsDesc( + ["aa.1", "aa.11", "aa.2", "b.ccc"] + )).toEqual( + ["b.ccc", "aa.11", "aa.2", "aa.1"] + ); + }); + + it("It Should Keep Invalid Indices at the Beginning and Sort Them Lexicographically", () => { + expect(slsIfElse.sortKeyPathsDesc( + ["aa.1", "aa.11", "aa.11z", "aa.2a"] + )).toEqual( + ["aa.2a", "aa.11z", "aa.11", "aa.1"] + ); + }); + + it("It Should Put Deeper Paths at the Beginning", () => { + expect(slsIfElse.sortKeyPathsDesc( + ["a.b", "a.b.c", "a.d.e", "a.d"] + )).toEqual( + ["a.d.e", "a.d", "a.b.c", "a.b"] + ); + }); +}); + const getServerless = function () { return { service: { @@ -94,6 +134,13 @@ const getServerless = function () { serverlessExclude: [], customCertificate: { enabled: false + }, + cors: { + headers: [ + "X-Prod-Specific-Header-1", + "Normal-Header", + "X-Prod-Specific-Header-2" + ] } }, provider: { @@ -167,7 +214,9 @@ const getConditions = function (condition) { If: '"true"=="true"', Exclude: [ "functions.func1", - "provider.role" + "provider.role", + "custom.cors.headers.0", + "custom.cors.headers.2", ], Set: { "provider.profile": "dev", @@ -211,6 +260,8 @@ const getConditions = function (condition) { ExcludeIf: { "functions.func3": '"true" == "true"', + "custom.cors.headers.0": '"true" == "true"', + "custom.cors.headers.2": '"true" == "true"', } } ], diff --git a/index.js b/index.js index 487200e..b0a7975 100644 --- a/index.js +++ b/index.js @@ -41,15 +41,20 @@ class serverlessPluginIfElse { } if (this.isvalidObject(item.ExcludeIf)) { - Object.keys(item.ExcludeIf).forEach((exludeKey) => { + let keyPaths = Object.keys(item.ExcludeIf); + + // Sort keyPaths to remove array items at correct indices + keyPaths = this.sortKeyPathsDesc(keyPaths); + + keyPaths.forEach((excludeKey) => { try { - if (eval(item.ExcludeIf[exludeKey])) { - this.conditionMatchLog(item.ExcludeIf[exludeKey], true); - this.changeKey(exludeKey); + if (eval(item.ExcludeIf[excludeKey])) { + this.conditionMatchLog(item.ExcludeIf[excludeKey], true); + this.changeKey(excludeKey); } } catch (e) { - this.evaluateErrorLog(item.ExcludeIf[exludeKey], e); + this.evaluateErrorLog(item.ExcludeIf[excludeKey], e); } }); } @@ -76,6 +81,54 @@ class serverlessPluginIfElse { this.changeKey(key, "set", items[key]); }); } + + /** + * @param {string} str + */ + isDigitsOnly(str) { + return /^\d+$/.test(str); + } + + /** + * Sorts key paths in descending order in such way that indices in the path + * are correctly treated as numbers. The original array is not touched. + * @param {string[]} keyPaths + */ + sortKeyPathsDesc(keyPaths) { + const keyPathsParsed = keyPaths.map(keyPath => keyPath.split(".")); + + keyPathsParsed.sort((pathA, pathB) => { + for (let i = 0; i < Math.min(pathA.length, pathB.length); i++) { + if (this.isDigitsOnly(pathA[i]) && this.isDigitsOnly(pathB[i])) { + const numA = parseInt(pathA[i]); + const numB = parseInt(pathB[i]); + if (numA < numB) { + return 1; + } else if (numA > numB) { + return -1; + } + } else { + // Use the default comparator + if (pathA[i] < pathB[i]) { + return 1; + } else if (pathA[i] > pathB[i]) { + return -1; + } + } + } + + if (pathA.length < pathB.length) { + return 1; + } else if (pathA.length > pathB.length) { + return -1; + } + + return 0; + }); + + return keyPathsParsed.map(path => path.join(".")); + } + /** * * @param {*} item @@ -85,6 +138,9 @@ class serverlessPluginIfElse { return; } if (typeof item == "object") { + // Sort keyPaths to remove array items at correct indices + item = this.sortKeyPathsDesc(item); + item.forEach((key) => { this.changeKey(key); }); @@ -113,7 +169,11 @@ class serverlessPluginIfElse { if (path[i] in item) { if (type == "remove") { this.serverless.cli.log(this.pluginName + " - Excluding: " + keyPath); - delete item[path[i]]; + if (Array.isArray(item)) { + item.splice(path[i], 1); + } else { + delete item[path[i]]; + } } else if (type == "set") { item[path[i]] = newValue; if (typeof newValue == "object") {