mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Merge pull request #1453 from serverless/better-errors
more validation and better error messages
This commit is contained in:
commit
c7335c3ee4
@ -21,9 +21,26 @@ module.exports.logError = (e) => {
|
||||
line = `${line}-`;
|
||||
}
|
||||
|
||||
consoleLog(' ');
|
||||
consoleLog(chalk.yellow(` ${errorType} ${line}`));
|
||||
consoleLog(' ');
|
||||
consoleLog(chalk.yellow(` ${e.message}`));
|
||||
|
||||
// format error message to fit neatly
|
||||
const words = e.message.split(' ');
|
||||
let logLine = [];
|
||||
words.forEach(word => {
|
||||
logLine.push(word);
|
||||
const logLineString = logLine.join(' ');
|
||||
if (logLineString.length > 50) {
|
||||
consoleLog(chalk.yellow(` ${logLineString}`));
|
||||
logLine = [];
|
||||
}
|
||||
});
|
||||
|
||||
if (logLine.length !== 0) {
|
||||
consoleLog(chalk.yellow(` ${logLine.join(' ')}`));
|
||||
}
|
||||
|
||||
consoleLog(' ');
|
||||
|
||||
if (e.name !== 'ServerlessError') {
|
||||
|
||||
@ -38,7 +38,11 @@ class PluginManager {
|
||||
validateCommands(commandsArray) {
|
||||
// TODO: implement an option to get deeper than one level
|
||||
if (!this.commands[commandsArray[0]]) {
|
||||
throw new this.serverless.classes.Error(`command "${commandsArray[0]}" not found.`);
|
||||
const errorMessage = [
|
||||
`command "${commandsArray[0]}" not found`,
|
||||
' Run "serverless help" for a list of all available commands.',
|
||||
].join();
|
||||
throw new this.serverless.classes.Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,15 @@ class Service {
|
||||
throw new SError('"functions" property is missing in serverless.yaml');
|
||||
}
|
||||
|
||||
if (['aws', 'azure', 'google', 'ibm'].indexOf(serverlessYaml.provider)) {
|
||||
const errorMessage = [
|
||||
`Provider "${serverlessYaml.provider}" is not supported.`,
|
||||
' Valid values for provider are: aws, azure, google, ibm.',
|
||||
' Please provide one of those values to the "provider" property in serverless.yaml.',
|
||||
].join('');
|
||||
throw new SError(errorMessage);
|
||||
}
|
||||
|
||||
that.service = serverlessYaml.service;
|
||||
that.provider = serverlessYaml.provider;
|
||||
that.variableSyntax = serverlessYaml.variableSyntax;
|
||||
@ -167,12 +176,22 @@ class Service {
|
||||
|
||||
// Populate
|
||||
if (!value && !value !== '') {
|
||||
const errorMessage = [
|
||||
`Variable "${variableName}" doesn't exist in serverless.env.yaml.`,
|
||||
' Please add it to serverless.env.yaml.',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error(`Variable "${variableName}" doesn't exist in serverless.env.yaml.`);
|
||||
.Error(errorMessage);
|
||||
} else if (typeof value === 'string') {
|
||||
if (variableString.split('.').length > 1) {
|
||||
const errorMessage = [
|
||||
`Trying to access sub properties of a string variable "${variableName}".`,
|
||||
' Please make sure the variable in serverless.env.yaml',
|
||||
' is an object, otherwise you cannot use the',
|
||||
' dot notation for that variable in serverless.yaml',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error('Trying to access sub properties of a string variable');
|
||||
.Error(errorMessage);
|
||||
}
|
||||
// for string variables, we use replaceall in case the user
|
||||
// includes the variable as a substring (ie. "hello ${name}")
|
||||
@ -185,8 +204,14 @@ class Service {
|
||||
subProperties.splice(0, 1);
|
||||
subProperties.forEach(subProperty => {
|
||||
if (!value[subProperty]) {
|
||||
const errorMessage = [
|
||||
`Variable "${variableName}" doesn't have sub property "${subProperty}".`,
|
||||
' Please make sure the variable is',
|
||||
' the intended object in serverless.env.yaml,',
|
||||
' or reference the correct sub property in serverless.yaml',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error(`Invalid sub property for variable "${variableName}"`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
value = value[subProperty];
|
||||
});
|
||||
@ -195,18 +220,35 @@ class Service {
|
||||
val = replaceall(variableSyntax, value, val);
|
||||
} else {
|
||||
if (val !== variableSyntax) {
|
||||
const errorMessage = [
|
||||
'Trying to populate non string variables into',
|
||||
` a string for variable "${variableName}".`,
|
||||
' Please make sure the variable value in',
|
||||
' serverless.env.yaml is a string',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error('Trying to populate non string variables into a string');
|
||||
.Error(errorMessage);
|
||||
}
|
||||
val = value;
|
||||
}
|
||||
} else if (variableString.split('.').length > 1) {
|
||||
const errorMessage = [
|
||||
`Trying to access sub properties of a non-object variable "${variableName}"`,
|
||||
' Please make sure the variable is an object in serverless.env.yaml,',
|
||||
' otherwise, you cannot use the dot notation',
|
||||
' for that variable in serverless.yaml',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error('Trying to access sub properties of a non-object variable');
|
||||
.Error(errorMessage);
|
||||
} else {
|
||||
if (val !== variableSyntax) {
|
||||
const errorMessage = [
|
||||
'Trying to populate non string variables',
|
||||
` into a string for variable "${variableName}".`,
|
||||
' Please make sure the variable value in serverless.env.yaml is a string',
|
||||
].join('');
|
||||
throw new that.serverless.classes
|
||||
.Error('Trying to populate non string variables into a string');
|
||||
.Error(errorMessage);
|
||||
}
|
||||
val = value; // not string nor object
|
||||
}
|
||||
@ -240,14 +282,14 @@ class Service {
|
||||
if (functionName in this.functions) {
|
||||
return this.functions[functionName];
|
||||
}
|
||||
throw new SError(`function ${functionName} doesn't exist in this Service`);
|
||||
throw new SError(`Function "${functionName}" doesn't exist in this Service`);
|
||||
}
|
||||
|
||||
getEventInFunction(eventName, functionName) {
|
||||
if (eventName in this.getFunction(functionName).events) {
|
||||
return this.getFunction(functionName).events[eventName];
|
||||
}
|
||||
throw new SError(`event ${eventName} doesn't exist in function ${functionName}`);
|
||||
throw new SError(`Event "${eventName}" doesn't exist in function "${functionName}"`);
|
||||
}
|
||||
|
||||
getAllEventsInFunction(functionName) {
|
||||
@ -258,7 +300,7 @@ class Service {
|
||||
if (stageName in this.environment.stages) {
|
||||
return this.environment.stages[stageName];
|
||||
}
|
||||
throw new SError(`stage ${stageName} doesn't exist in this Service`);
|
||||
throw new SError(`Stage "${stageName}" doesn't exist in this service.`);
|
||||
}
|
||||
|
||||
getAllStages() {
|
||||
@ -269,7 +311,7 @@ class Service {
|
||||
if (regionName in this.getStage(stageName).regions) {
|
||||
return this.getStage(stageName).regions[regionName];
|
||||
}
|
||||
throw new SError(`region ${regionName} doesn't exist in stage ${stageName}`);
|
||||
throw new SError(`Region "${regionName}" doesn't exist in stage "${stageName}"`);
|
||||
}
|
||||
|
||||
getAllRegionsInStage(stageName) {
|
||||
|
||||
@ -18,8 +18,14 @@ module.exports = {
|
||||
method = event.http.split(' ')[0];
|
||||
path = event.http.split(' ')[1];
|
||||
} else {
|
||||
const errorMessage = [
|
||||
`HTTP event of function ${functionName} is not an object nor a string.`,
|
||||
' The correct syntax is: http: get users/list',
|
||||
' OR an object with "path" and "method" proeprties.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`HTTP event of function ${functionName} is not an object nor a string`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
const resourceLogicalId = this.resourceLogicalIds[path];
|
||||
|
||||
@ -18,8 +18,14 @@ module.exports = {
|
||||
method = event.http.split(' ')[0];
|
||||
path = event.http.split(' ')[1];
|
||||
} else {
|
||||
const errorMessage = [
|
||||
`HTTP event of function ${functionName} is not an object nor a string.`,
|
||||
' The correct syntax is: http: get users/list',
|
||||
' OR an object with "path" and "method" proeprties.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`HTTP event of function ${functionName} is not an object nor a string`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
const normalizedMethod = method[0].toUpperCase() +
|
||||
|
||||
@ -18,8 +18,14 @@ module.exports = {
|
||||
} else if (typeof event.http === 'string') {
|
||||
path = event.http.split(' ')[1];
|
||||
} else {
|
||||
const errorMessage = [
|
||||
`HTTP event of function ${functionName} is not an object nor a string.`,
|
||||
' The correct syntax is: http: get users/list',
|
||||
' OR an object with "path" and "method" proeprties.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`HTTP event of function ${functionName} is not an object nor a string`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
while (path !== '') {
|
||||
|
||||
@ -22,7 +22,7 @@ module.exports = {
|
||||
this.serverless.service.getRegionInStage(this.options.stage, this.options.region);
|
||||
|
||||
// validate that path and method exists for each http event in service
|
||||
forEach(this.serverless.service.functions, (functionObject) => {
|
||||
forEach(this.serverless.service.functions, (functionObject, functionName) => {
|
||||
functionObject.events.forEach(event => {
|
||||
if (event.http) {
|
||||
let method;
|
||||
@ -36,14 +36,35 @@ module.exports = {
|
||||
path = event.http.split(' ')[1];
|
||||
}
|
||||
|
||||
// TODO validate the values of these properties as well
|
||||
if (!path) {
|
||||
const errorMessage = [
|
||||
`Missing "path" property in function "${functionName}"`,
|
||||
' for http event in serverless.yaml.',
|
||||
' If you define an http event, make sure you pass a valid value for it,',
|
||||
' either as string syntax, or object syntax.',
|
||||
' Please check the docs for more options.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error('Missing "path" property in serverless.yaml for http event.');
|
||||
.Error(errorMessage);
|
||||
}
|
||||
if (!method) {
|
||||
const errorMessage = [
|
||||
`Missing "method" property in function "${functionName}"`,
|
||||
' for http event in serverless.yaml.',
|
||||
' If you define an http event, make sure you pass a valid value for it,',
|
||||
' either as string syntax, or object syntax.',
|
||||
' Please check the docs for more options.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error('Missing "method" property in serverless.yaml for http event.');
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
if (['get', 'post', 'put', 'patch', 'options', 'head', 'delete'].indexOf(method) === -1) {
|
||||
const errorMessage = [
|
||||
`Invalid APIG method "${method}" in function "${functionName}".`,
|
||||
' AWS supported methods are: get, post, put, patch, options, head, delete.',
|
||||
].join('');
|
||||
throw new this.serverless.classes.Error(errorMessage);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -31,8 +31,13 @@ class AwsCompileS3Events {
|
||||
|
||||
if (typeof event.s3 === 'object') {
|
||||
if (!event.s3.bucket) {
|
||||
const errorMessage = [
|
||||
`Missing "bucket" property for s3 event in function ${functionName}.`,
|
||||
' The correct syntax is: s3: bucketName OR an object with "bucket" property.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`Missing "bucket" property in s3 event in function ${functionName}`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
BucketName = event.s3.bucket + i;
|
||||
if (event.s3.event) {
|
||||
@ -41,8 +46,13 @@ class AwsCompileS3Events {
|
||||
} else if (typeof event.s3 === 'string') {
|
||||
BucketName = event.s3 + i;
|
||||
} else {
|
||||
const errorMessage = [
|
||||
`S3 event of function ${functionName} is not an object nor a string.`,
|
||||
' The correct syntax is: s3: bucketName OR an object with "bucket" property.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`S3 event of function ${functionName} is not an object nor a string`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
const bucketTemplate = `
|
||||
{
|
||||
|
||||
@ -31,8 +31,14 @@ class AwsCompileScheduledEvents {
|
||||
// TODO validate rate syntax
|
||||
if (typeof event.schedule === 'object') {
|
||||
if (!event.schedule.rate) {
|
||||
const errorMessage = [
|
||||
`Missing "rate" property for schedule event in function ${functionName}`,
|
||||
' The correct syntax is: schedule: rate(10 minutes)',
|
||||
' OR an object with "rate" property.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`Missing "rate" property in schedule event in function ${functionName}`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
ScheduleExpression = event.schedule.rate;
|
||||
State = event.schedule.enabled ? 'ENABLED' : 'DISABLED';
|
||||
@ -40,8 +46,14 @@ class AwsCompileScheduledEvents {
|
||||
ScheduleExpression = event.schedule;
|
||||
State = 'ENABLED';
|
||||
} else {
|
||||
const errorMessage = [
|
||||
`Schedule event of function ${functionName} is not an object nor a string`,
|
||||
' The correct syntax is: schedule: rate(10 minutes)',
|
||||
' OR an object with "rate" property.',
|
||||
' Please check the docs for more info.',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`Schedule event of function ${functionName} is not an object nor a string`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
const scheduleTemplate = `
|
||||
|
||||
@ -48,19 +48,24 @@ class AwsCompileFunctions {
|
||||
.S3Key = ''; // will be replaced in a further step
|
||||
|
||||
if (!functionObject.handler) {
|
||||
const errorMessage = [
|
||||
`Missing "handler" property in function ${functionName}`,
|
||||
' Please make sure you point to the correct lambda handler.',
|
||||
' For example: handler.hello.',
|
||||
' Please check the docs for more info',
|
||||
].join('');
|
||||
throw new this.serverless.classes
|
||||
.Error(`Missing "handler" property in function ${functionName}`);
|
||||
.Error(errorMessage);
|
||||
}
|
||||
|
||||
// TODO validate the values of each of those properties
|
||||
const Handler = functionObject.handler;
|
||||
const FunctionName = functionObject.name
|
||||
|| `${this.serverless.service.service}-${this.options.stage}-${functionName}`;
|
||||
const MemorySize = functionObject.memory
|
||||
|| this.serverless.service.defaults.memory
|
||||
const MemorySize = Number(functionObject.memory)
|
||||
|| Number(this.serverless.service.defaults.memory)
|
||||
|| 1024;
|
||||
const Timeout = functionObject.timeout
|
||||
|| this.serverless.service.defaults.timeout
|
||||
const Timeout = Number(functionObject.timeout)
|
||||
|| Number(this.serverless.service.defaults.timeout)
|
||||
|| 6;
|
||||
|
||||
newFunction.Properties.Handler = Handler;
|
||||
|
||||
@ -51,7 +51,12 @@ module.exports = {
|
||||
const zipFileName = `${func.name}-${(new Date).getTime().toString()}.zip`;
|
||||
|
||||
if (!handlerFullPath.endsWith(func.handler)) {
|
||||
throw new this.serverless.classes.Error(`The handler ${func.handler} was not found`);
|
||||
const errorMessage = [
|
||||
`The handler ${func.handler} was not found.`,
|
||||
' Please make sure you have this handler in your service at the referenced location.',
|
||||
' Please check the docs for more info',
|
||||
].join('');
|
||||
throw new this.serverless.classes.Error(errorMessage);
|
||||
}
|
||||
|
||||
const packageRoot = handlerFullPath.replace(func.handler, '');
|
||||
|
||||
@ -5,7 +5,8 @@ const BbPromise = require('bluebird');
|
||||
module.exports = {
|
||||
validate() {
|
||||
if (!this.serverless.config.servicePath) {
|
||||
throw new this.serverless.classes.Error('This command can only be run inside a service');
|
||||
throw new this.serverless.classes
|
||||
.Error('This command can only be run inside a service directory');
|
||||
}
|
||||
|
||||
this.options.stage = this.options.stage
|
||||
@ -19,12 +20,6 @@ module.exports = {
|
||||
this.serverless.service.getStage(this.options.stage);
|
||||
this.serverless.service.getRegionInStage(this.options.stage, this.options.region);
|
||||
|
||||
if (Object.keys(this.serverless.service.environment
|
||||
.stages[this.options.stage].regions[this.options.region]).indexOf('vars') === -1) {
|
||||
throw new this.serverless.classes
|
||||
.Error('region vars object does not exist in serverless.env.yaml');
|
||||
}
|
||||
|
||||
return BbPromise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
@ -54,10 +54,5 @@ describe('#validate()', () => {
|
||||
awsDeploy.serverless.config.servicePath = false;
|
||||
expect(() => awsDeploy.validate()).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should throw error if region vars object does not exist', () => {
|
||||
awsDeploy.serverless.service.environment.stages.dev.regions['us-east-1'] = {};
|
||||
expect(() => awsDeploy.validate()).to.throw(Error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ class AwsInvoke {
|
||||
if (this.options.path) {
|
||||
if (!this.serverless.utils
|
||||
.fileExistsSync(path.join(this.serverless.config.servicePath, this.options.path))) {
|
||||
throw new this.serverless.classes.Error('The file path you provided does not exist.');
|
||||
throw new this.serverless.classes.Error('The file you provided does not exist.');
|
||||
}
|
||||
|
||||
this.options.data = this.serverless.utils
|
||||
|
||||
@ -49,7 +49,11 @@ class Create {
|
||||
}
|
||||
|
||||
if (['aws', 'azure', 'google', 'ibm'].indexOf(this.options.provider)) {
|
||||
throw new this.serverless.classes.Error('Please provide a valid provider.');
|
||||
const errorMessage = [
|
||||
`Provider "${this.options.provider}" is not supported.`,
|
||||
' Valid values for provider are: aws, azure, google, ibm.',
|
||||
].join('');
|
||||
throw new this.serverless.classes.Error(errorMessage);
|
||||
}
|
||||
|
||||
this.serverless.config
|
||||
|
||||
@ -507,6 +507,45 @@ describe('Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if provider property is invalid', () => {
|
||||
const SUtils = new Utils();
|
||||
const tmpDirPath = path.join(os.tmpdir(), (new Date).getTime().toString());
|
||||
const serverlessYaml = {
|
||||
service: 'service-name',
|
||||
provider: 'invalid',
|
||||
functions: {},
|
||||
};
|
||||
const serverlessEnvYaml = {
|
||||
vars: {},
|
||||
stages: {
|
||||
dev: {
|
||||
vars: {},
|
||||
regions: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
serverlessEnvYaml.stages.dev.regions['us-east-1'] = {
|
||||
vars: {},
|
||||
};
|
||||
|
||||
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'),
|
||||
YAML.dump(serverlessYaml));
|
||||
SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.env.yaml'),
|
||||
YAML.dump(serverlessEnvYaml));
|
||||
|
||||
const serverless = new Serverless({ servicePath: tmpDirPath });
|
||||
serviceInstance = new Service(serverless);
|
||||
|
||||
return serviceInstance.load().then(() => {
|
||||
// if we reach this, then no error was thrown as expected
|
||||
// so make assertion fail intentionally to let us know something is wrong
|
||||
expect(1).to.equal(2);
|
||||
}).catch(e => {
|
||||
expect(e.name).to.be.equal('ServerlessError');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if functions property is missing', () => {
|
||||
const SUtils = new Utils();
|
||||
const tmpDirPath = path.join(os.tmpdir(), (new Date).getTime().toString());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user