Repository: serverless/serverless (JavaScript)
Date: 2018-12-30 12:27:42+00:00
Task type: Feature

Issue:
Print warning when 2 functions have same handler
# This is a Feature Proposal

## Description

Whenever I add a new ƛ I copy-paste last lines of code in `serverless.yml` file. Most of the time they are similar, so I just write a new handler and rename it (in `.yml`). 

But omg, it's too damn often when I forget to change the path of handler. When deployed I end up in a bizarre situation when I call 1 lambda but another is invoked. My coworkers often catch this during PR review, but still, not always.

I'd like to see at least a warning during deployment when 2 fns have exactly same path and handler.

Please tell me if this is a valid feature request or is it only my unique problem?



![2017-10-25 at 18 47](https://user-images.githubusercontent.com/3817380/32008621-5a4def06-b9b5-11e7-8d56-1f54d2803e6f.gif)




Issue discussion:
True story! :+1: 
Waiting for this feature!
Hey, @vladgolubev! Thank you for reporting this issue :+1: 
I think that's a valid feature request :100: 

Could you share the `serverless.yml` when you face the bizarre situation so that we can take a look into it?
The validation should be done when importing the serverless.yml imo. Then it can break out at the earliest possibility in case two function declarations point to the same handler path (*AND* exported function).
@horike37 This is not a bug. It is an enhancement to warn about "user-generated" errors. I'll change the tag.
@horike37 Just added a GIF to the description.  @HyperBrain is right, this is kind of enhancement to catch dumb user errors and improve DX
@vladgolubev 🥇 for the great animated GIF 😄 

Solution patch:
diff --git a/lib/classes/Service.js b/lib/classes/Service.js
index 293b4295447..4a16da7e303 100644
--- a/lib/classes/Service.js
+++ b/lib/classes/Service.js
@@ -208,6 +208,28 @@ class Service {
       }
     });
 
+    const warnOnDuplicateHandlers = () => {
+      const functionObjs = this.getAllFunctions()
+        .map(name => ({
+          name,
+          body: this.getFunction(name),
+        }))
+        .filter(func => _.isString(func.body.handler));
+      const groupedByHandler = _.groupBy(functionObjs, func => func.body.handler);
+      _.entriesIn(groupedByHandler)
+        .filter(arg => arg[1].length >= 2)
+        .forEach(arg => {
+          const handlerName = arg[0];
+          const functionGroup = arg[1].map(func => func.name);
+          const warnMessage = [
+            `Warning: A handler "${handlerName}" is duplicated in functions: `,
+            `${functionGroup.join(', ')}.`,
+          ].join('');
+          this.serverless.cli.log(warnMessage);
+        });
+    };
+    warnOnDuplicateHandlers();
+
     return this;
   }
 


Test patch:
diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js
index 57372ec8d1a..7f71031ce4d 100644
--- a/lib/classes/Service.test.js
+++ b/lib/classes/Service.test.js
@@ -9,6 +9,7 @@ const Service = require('../../lib/classes/Service');
 const Utils = require('../../lib/classes/Utils');
 const Serverless = require('../../lib/Serverless');
 const testUtils = require('../../tests/utils');
+const CLI = require('../../lib/classes/CLI');
 
 // Configure chai
 chai.use(require('chai-as-promised'));
@@ -722,6 +723,46 @@ describe('Service', () => {
           .to.throw('Events for "functionA" must be an array, not an string');
       });
     });
+
+    it('should warn if multiple functions have same handler', () => {
+      const SUtils = new Utils();
+      const serverlessYml = {
+        service: 'testService',
+        provider: 'testProvider',
+        functions: {
+          functionA: {
+            handler: 'foo.functionA',
+            events: [],
+          },
+          functionB: {
+            handler: 'foo.functionA',
+            events: [],
+          },
+          function123: {
+            handler: 'foo.function123',
+            events: [],
+          },
+          function456: {
+            handler: 'foo.function123',
+            events: [],
+          },
+        },
+      };
+      SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'),
+        YAML.dump(serverlessYml));
+
+      const serverless = new Serverless({ servicePath: tmpDirPath });
+
+      class CustomCLI extends CLI {}
+      const consoleLogStub = sinon.stub(CustomCLI.prototype, 'log');
+      serverless.cli = new CustomCLI(serverless);
+
+      return expect(serverless.service.load()).to.eventually.be.fulfilled
+      .then(() => {
+        serverless.service.validate();
+        expect(consoleLogStub.callCount).to.equal(2);
+      });
+    });
   });
 
   describe('#mergeArrays', () => {