New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix awsProvider.js : "Cannot use 'in' operator to search for '0' #5688

Merged
merged 1 commit into from Jan 17, 2019
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+153 −122
Diff settings

Always

Just for now

Copy path View file
@@ -6,6 +6,8 @@ const _ = require('lodash');
const BbPromise = require('bluebird'); const BbPromise = require('bluebird');
const semver = require('semver'); const semver = require('semver');


const validAPIGatewayStageNamePattern = /^[-_a-zA-Z0-9]+$/;

class Service { class Service {
constructor(serverless, data) { constructor(serverless, data) {
// ####################################################################### // #######################################################################
@@ -230,6 +232,22 @@ class Service {
}; };
warnOnDuplicateHandlers(); warnOnDuplicateHandlers();


const provider = this.serverless.getProvider('aws');
if (provider) {
const stage = provider.getStage();
this.getAllFunctions().forEach(funcName => {

This comment has been minimized.

@horike37

horike37 Jan 16, 2019

Member

Is it possible to use BbPromise.each function instead of forEach ? We're currently enforcing the policy that all new code should be as async as possible since sync calls are on of the roots of the sloweness of the Framework.

This comment has been minimized.

@exoego

exoego Jan 17, 2019

Author Contributor

Yes, it can be async technically.
But I think it is premature optimization and not necessary for this case.

Both Service#getAllFunctions and Service#getAllEventsInFunction used in validation are just iterating over functions (and its events).
I assume number of those are several dozens in most cases, and thousands in very extreme situation.
I believe Node.js is fast enough for such number of elements.

And, other validations in Service#validate are also iterating over functions synchronousely.
I can make them all asynchronously, but it requires changes a lot in existing tests.
I think it is better to optimize in another PR when users complains.

This comment has been minimized.

@horike37

horike37 Jan 17, 2019

Member

@exoego
OK 👍 Thank you for considering it 😄

_.forEach(this.getAllEventsInFunction(funcName), event => {
if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) {
throw new this.serverless.classes.Error([
`Invalid stage name ${stage}:`,
'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
}
});
});
}

return this; return this;
} }


Copy path View file
@@ -763,6 +763,141 @@ describe('Service', () => {
expect(consoleLogStub.callCount).to.equal(2); expect(consoleLogStub.callCount).to.equal(2);
}); });
}); });

describe('stage name validation', () => {
function simulateRun(serverless) {

This comment has been minimized.

@exoego

exoego Jan 16, 2019

Author Contributor

Note: simulateRun is a simplified emulation of Serverless#run.
This is a bit tricky but required to populate function names, I think.

return serverless.init().then(() =>
serverless.variables.populateService(serverless.pluginManager.cliOptions)
.then(() => {
serverless.service.mergeArrays();
serverless.service.setFunctionNames(serverless.processedInput.options);
}));
}

it(`should not throw an error if http event is absent and
stage contains only alphanumeric, underscore and hyphen`, () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'xyz-101_abc-123',
},
functions: {
first: {
events: [],
},
},
};
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'),
YAML.dump(serverlessYml));

const serverless = new Serverless({ servicePath: tmpDirPath });
return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => {
expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error);
});
});

it(`should not throw an error after variable population if http event is present and
the populated stage contains only alphanumeric, underscore and hyphen`, () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default-stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'),
YAML.dump(serverlessYml));

const serverless = new Serverless({ servicePath: tmpDirPath });
return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => {
expect(() => serverless.service.validate()).to.not.throw(serverless.classes.Error);
});
});

it('should throw an error if http event is present and stage contains invalid chars', () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'my@stage',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'),
YAML.dump(serverlessYml));

const serverless = new Serverless({ servicePath: tmpDirPath });
return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => {
expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [
'Invalid stage name my@stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});
});

it(`should throw an error after variable population
if http event is present and stage contains hyphen`, () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default:stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yml'),
YAML.dump(serverlessYml));

const serverless = new Serverless({ servicePath: tmpDirPath });
return expect(simulateRun(serverless)).to.eventually.be.fulfilled.then(() => {
expect(() => serverless.service.validate()).to.throw(serverless.classes.Error, [
'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});
});
});
}); });


describe('#mergeArrays', () => { describe('#mergeArrays', () => {
Copy path View file
@@ -10,8 +10,6 @@ const fse = require('../utils/fs/fse');
const logWarning = require('./Error').logWarning; const logWarning = require('./Error').logWarning;
const PromiseTracker = require('./PromiseTracker'); const PromiseTracker = require('./PromiseTracker');


const validAPIGatewayStageNamePattern = /^[-_a-zA-Z0-9]+$/;

/** /**
* Maintainer's notes: * Maintainer's notes:
* *
@@ -110,19 +108,6 @@ class Variables {
this.populateValue(config.value, true) // populate this.populateValue(config.value, true) // populate
.then(populated => _.assign(config, { populated }))); .then(populated => _.assign(config, { populated })));
return this.assignProperties(provider, prepopulations); return this.assignProperties(provider, prepopulations);
}).then(() => {
const stage = provider.getStage();
this.serverless.service.getAllFunctions().forEach(funcName => {
_.forEach(this.serverless.service.getAllEventsInFunction(funcName), event => {
if (_.has(event, 'http') && !validAPIGatewayStageNamePattern.test(stage)) {
throw new this.serverless.classes.Error([
`Invalid stage name ${stage}:`,
'it should contains only [-_a-zA-Z0-9] for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
}
});
});
}); });
} }
return BbPromise.resolve(); return BbPromise.resolve();
Copy path View file
@@ -207,113 +207,6 @@ describe('Variables', () => {
}); });
}); });
}); });
describe('stage name validation', () => {
it(`should not throw an error if http event is absent and
stage contains only alphanumeric, underscore and hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'xyz-101_abc-123',
},
functions: {
first: {
events: [],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be.fullfilled;
});

it(`should not throw an error after variable population if http event is present and
the populated stage contains only alphanumeric, underscore and hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default-stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be.fullfilled;
});

it('should throw an error if http event is present and stage contains invalid chars', () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: 'my@stage',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be
.rejectedWith(serverless.classes.Error, [
'Invalid stage name my@stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});

it(`should throw an error after variable population
if http event is present and stage contains hyphen`, () => {
const serverlessYml = {
service: 'new-service',
provider: {
name: 'aws',
stage: '${opt:stage, "default:stage"}',
},
functions: {
first: {
events: [
{
http: {
path: 'foo',
method: 'GET',
},
},
],
},
},
};
serverless.service = new serverless.classes.Service(serverless, serverlessYml);

return serverless.variables.populateService().should.be
.rejectedWith(serverless.classes.Error, [
'Invalid stage name default:stage: it should contains only [-_a-zA-Z0-9]',
'for AWS provider if http event are present',
'according to API Gateway limitation.',
].join(' '));
});
});
}); });


describe('#getProperties', () => { describe('#getProperties', () => {
ProTip! Use n and p to navigate between commits in a pull request.