Skip to content

Commit

Permalink
refactor: Construct user errors with ServerlessError
Browse files Browse the repository at this point in the history
  • Loading branch information
medikoo committed May 19, 2021
1 parent 277d66c commit c563581
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 35 deletions.
23 changes: 15 additions & 8 deletions lib/classes/PluginManager.js
Expand Up @@ -372,24 +372,31 @@ class PluginManager {
};
}
if (!_.isObject(options)) {
throw new Error(
throw new ServerlessError(
`Custom variable resolver {${variablePrefix}: ${JSON.stringify(
options
)}} defined by ${pluginName} isn't an object`
)}} defined by ${pluginName} isn't an object`,
'CUSTOM_LEGACY_VARIABLES_RESOLVER_INVALID_FORMAT'
);
} else if (!variablePrefix.match(/[0-9a-zA-Z_-]+/)) {
throw new Error(
`Custom variable resolver prefix ${variablePrefix} defined by ${pluginName} may only contain alphanumeric characters, hyphens or underscores.`
throw new ServerlessError(
`Custom variable resolver prefix ${variablePrefix} defined by ${pluginName} ` +
'may only contain alphanumeric characters, hyphens or underscores.',
'CUSTOM_LEGACY_VARIABLES_RESOLVER_INVALID_PREFIX'
);
}
if (typeof options.resolver !== 'function') {
throw new Error(
`Custom variable resolver for ${variablePrefix} defined by ${pluginName} specifies a resolver that isn't a function: ${options.resolver}`
throw new ServerlessError(
`Custom variable resolver for ${variablePrefix} defined by ${pluginName} ` +
`specifies a resolver that isn't a function: ${options.resolver}`,
'CUSTOM_LEGACY_VARIABLES_RESOLVER_INVALID_FORMAT'
);
}
if (options.isDisabledAtPrepopulation && typeof options.serviceName !== 'string') {
throw new Error(
`Custom variable resolver for ${variablePrefix} defined by ${pluginName} specifies isDisabledAtPrepopulation but doesn't provide a string for a name`
throw new ServerlessError(
`Custom variable resolver for ${variablePrefix} defined by ${pluginName} ` +
"specifies isDisabledAtPrepopulation but doesn't provide a string for a name",
'CUSTOM_LEGACY_VARIABLES_RESOLVER_INVALID_CONFIGURATION'
);
}
this.serverless.variables.variableResolvers.push({
Expand Down
5 changes: 4 additions & 1 deletion lib/classes/Service.js
Expand Up @@ -206,7 +206,10 @@ class Service {
if (typeof value === 'object') {
return _.merge(memo, value);
}
throw new Error(`Non-object value specified in ${key} array: ${value}`);
throw new ServerlessError(
`Non-object value specified in ${key} array: ${value}`,
'LEGACY_CONFIGURATION_PROPERTY_MERGE_INVALID_INPUT'
);
}

return memo;
Expand Down
30 changes: 25 additions & 5 deletions lib/configuration/read.js
Expand Up @@ -16,27 +16,47 @@ const resolveTsNode = async (serviceDir) => {
try {
return (await resolveModulePath(__dirname, 'ts-node')).realPath;
} catch (slsDepError) {
if (slsDepError.code !== 'MODULE_NOT_FOUND') throw slsDepError;
if (slsDepError.code !== 'MODULE_NOT_FOUND') {
throw new ServerlessError(
`Cannot resolve "ts-node" due to: ${slsDepError.message}`,
'TS_NODE_RESOLUTION_ERROR'
);
}

// 2. If installed in a service, use it
try {
return (await resolveModulePath(serviceDir, 'ts-node')).realPath;
} catch (serviceDepError) {
if (serviceDepError.code !== 'MODULE_NOT_FOUND') throw serviceDepError;
if (serviceDepError.code !== 'MODULE_NOT_FOUND') {
throw new ServerlessError(
`Cannot resolve "ts-node" due to: ${serviceDepError.message}`,
'TS_NODE_IN_SERVICE_RESOLUTION_ERROR'
);
}

// 3. If installed globally, use it
const { stdoutBuffer } = await (async () => {
try {
return await spawn('npm', ['root', '-g']);
} catch (error) {
if (error.code !== 'ENOENT') throw error;
throw new Error('"ts-node" not found');
if (error.code !== 'ENOENT') {
throw new ServerlessError(
`Cannot resolve "ts-node" due to unexpected "npm" error: ${error.message}`,
'TS_NODE_NPM_RESOLUTION_ERROR'
);
}
throw new ServerlessError('"ts-node" not found', 'TS_NODE_NOT_FOUND');
}
})();
try {
return require.resolve(`${String(stdoutBuffer).trim()}/ts-node`);
} catch (globalDepError) {
if (globalDepError.code !== 'MODULE_NOT_FOUND') throw globalDepError;
if (globalDepError.code !== 'MODULE_NOT_FOUND') {
throw new ServerlessError(
`Cannot resolve "ts-node" due to: ${globalDepError.message}`,
'TS_NODE_NPM_GLOBAL_RESOLUTION_ERROR'
);
}
throw new ServerlessError('"ts-node" not found', 'TS_NODE_NOT_FOUND');
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/plugins/aws/deployFunction.js
Expand Up @@ -116,7 +116,10 @@ class AwsDeployFunction {
const roleResource = this.serverless.service.resources.Resources[role];

if (roleResource.Type !== 'AWS::IAM::Role') {
throw new Error('Provided resource is not IAM Role.');
throw new ServerlessError(
'Provided resource is not IAM Role',
'ROLE_REFERENCES_NON_AWS_IAM_ROLE'
);
}
const roleProperties = roleResource.Properties;
if (!roleProperties.RoleName) {
Expand Down
40 changes: 32 additions & 8 deletions lib/plugins/aws/invokeLocal/index.js
Expand Up @@ -198,7 +198,10 @@ class AwsInvokeLocal {
} else if (value.Ref) {
configuredEnvVars[name] = await resolveCfRefValue(this.provider, value.Ref);
} else {
throw new Error(`Unsupported format: ${inspect(value)}`);
throw new ServerlessError(
`Unsupported environment variable format: ${inspect(value)}`,
'INVOKE_LOCAL_UNSUPPORTED_ENV_VARIABLE'
);
}
} catch (error) {
throw new ServerlessError(
Expand Down Expand Up @@ -282,7 +285,10 @@ class AwsInvokeLocal {
try {
return await spawnExt('docker', ['version']);
} catch {
throw new Error('Please start the Docker daemon to use the invoke local Docker integration.');
throw new ServerlessError(
'Please start the Docker daemon to use the invoke local Docker integration.',
'DOCKER_DAEMON_NOT_FOUND'
);
}
}

Expand Down Expand Up @@ -410,7 +416,10 @@ class AwsInvokeLocal {
if (err.stdBuffer) {
process.stdout.write(err.stdBuffer);
}
throw new Error(`Failed to build docker image (exit code ${err.code}})`);
throw new ServerlessError(
`Failed to build docker image (exit code ${err.code}})`,
'DOCKER_IMAGE_BUILD_FAILED'
);
}
}

Expand Down Expand Up @@ -518,7 +527,10 @@ class AwsInvokeLocal {
if (err.stdBuffer) {
process.stdout.write(err.stdBuffer);
}
throw new Error(`Failed to run docker for ${runtime} image (exit code ${err.code}})`);
throw new ServerlessError(
`Failed to run docker for ${runtime} image (exit code ${err.code}})`,
'DOCKER_IMAGE_RUN_FAILED'
);
}
}

Expand Down Expand Up @@ -625,7 +637,10 @@ class AwsInvokeLocal {
try {
await fse.stat(artifactPath);
} catch {
throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`);
throw new ServerlessError(
`Artifact ${artifactPath} doesn't exists, please compile it first.`,
'JAVA_ARTIFACT_NOT_FOUND'
);
}
const timeout =
Number(this.options.functionObj.timeout) ||
Expand Down Expand Up @@ -685,7 +700,10 @@ class AwsInvokeLocal {
}
isRejected = true;
reject(
new Error(`Failed to build the Java bridge. exit code=${code} signal=${signal}`)
new ServerlessError(
`Failed to build the Java bridge. exit code=${code} signal=${signal}`,
'JAVA_BRIDGE_BUILD_FAILED'
)
);
}
});
Expand Down Expand Up @@ -746,7 +764,10 @@ class AwsInvokeLocal {
lambda = handlersContainer[handlerName];
} catch (error) {
this.serverless.cli.consoleLog(chalk.red(inspect(error)));
throw new Error(`Exception encountered when loading ${pathToHandler}`);
throw new ServerlessError(
`Exception encountered when loading ${pathToHandler}`,
'INVOKE_LOCAL_LAMBDA_INITIALIZATION_FAILED'
);
}

if (typeof lambda !== 'function') {
Expand Down Expand Up @@ -785,7 +806,10 @@ class AwsInvokeLocal {
body: JSON.parse(result.body),
});
} catch (e) {
throw new Error('Content-Type of response is application/json but body is not json');
throw new ServerlessError(
'Content-Type of response is application/json but body is not json',
'INVOKE_LOCAL_RESPONSE_TYPE_MISMATCH'
);
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions lib/plugins/aws/package/compile/events/cloudFront.js
Expand Up @@ -262,13 +262,15 @@ class AwsCompileCloudFrontEvents {
const { eventType = 'default' } = cloudFront;
const { maxMemorySize, maxTimeout } = this.lambdaEdgeLimits[eventType];
if (functionObj.memorySize && functionObj.memorySize > maxMemorySize) {
throw new Error(
`"${functionName}" memorySize is greater than ${maxMemorySize} which is not supported by Lambda@Edge functions of type "${eventType}"`
throw new ServerlessError(
`"${functionName}" memorySize is greater than ${maxMemorySize} which is not supported by Lambda@Edge functions of type "${eventType}"`,
'LAMBDA_EDGE_UNSUPPORTED_MEMORY_SIZE'
);
}
if (functionObj.timeout && functionObj.timeout > maxTimeout) {
throw new Error(
`"${functionName}" timeout is greater than ${maxTimeout} which is not supported by Lambda@Edge functions of type "${eventType}"`
throw new ServerlessError(
`"${functionName}" timeout is greater than ${maxTimeout} which is not supported by Lambda@Edge functions of type "${eventType}"`,
'LAMBDA_EDGE_UNSUPPORTED_TIMEOUT_VALUE'
);
}
});
Expand Down
6 changes: 4 additions & 2 deletions lib/plugins/aws/package/lib/mergeCustomProviderResources.js
@@ -1,6 +1,7 @@
'use strict';

const _ = require('lodash');
const ServerlessError = require('../../../../serverless-error');

module.exports = {
mergeCustomProviderResources() {
Expand Down Expand Up @@ -71,11 +72,12 @@ module.exports = {

// default includes any future attributes we don't know about yet.
default:
throw new Error(
throw new ServerlessError(
`${resourceName}: Sorry, extending the ${extensionAttributeName} resource ` +
'attribute at this point is not supported. Feel free to propose support ' +
'for it in the Framework issue tracker: ' +
'https://github.com/serverless/serverless/issues'
'https://github.com/serverless/serverless/issues',
'RESOURCE_EXTENSION_UNSUPPORTED_ATTRIBUTE'
);
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/plugins/aws/provider.js
Expand Up @@ -1588,7 +1588,10 @@ class AwsProvider {
if (!alias) return arnGetter;
return { 'Fn::Join': [':', [arnGetter, alias.name]] };
}
throw new Error(`Unrecognized function address ${functionAddress}`);
throw new ServerlessError(
`Unrecognized function address ${functionAddress}`,
'UNRECOGNIZED_FUNCTION_ADDRESS'
);
}

resolveLayerArtifactName(layerName) {
Expand Down
8 changes: 6 additions & 2 deletions lib/plugins/aws/rollbackFunction.js
@@ -1,6 +1,7 @@
'use strict';

const BbPromise = require('bluebird');
const ServerlessError = require('../../serverless-error');
const validate = require('./lib/validate');
const fetch = require('node-fetch');

Expand Down Expand Up @@ -49,9 +50,12 @@ class AwsRollbackFunction {
` and version "${funcVersion}" is available for this function.`,
' Please check the docs for more info.',
].join('');
throw new Error(errorMessage);
throw new ServerlessError(errorMessage, 'AWS_FUNCTION_NOT_FOUND');
}
throw new Error(error.message);
throw new ServerlessError(
`Cannot resolve function "${funcName}": ${error.message}`,
'AWS_FUNCTION_NOT_ACCESIBLE'
);
});
}

Expand Down
8 changes: 7 additions & 1 deletion lib/plugins/aws/utils/credentials.js
Expand Up @@ -4,6 +4,7 @@ const { join } = require('path');
const { constants, readFile, writeFile, mkdir } = require('fs');
const os = require('os');
const BbPromise = require('bluebird');
const ServerlessError = require('../../../serverless-error');

const homedir = os.homedir();
const awsConfigDirPath = join(homedir, '.aws');
Expand Down Expand Up @@ -101,7 +102,12 @@ module.exports = {

saveFileProfiles(profiles) {
return new BbPromise((resolve) => {
if (!credentialsFilePath) throw new Error('Could not resolve path to user credentials file');
if (!credentialsFilePath) {
throw new ServerlessError(
'Could not resolve path to user credentials file',
'UNKNOWN_AWS_CREDENTIALS_PATH'
);
}
resolve(
writeCredentialsContent(
`${Array.from(profiles)
Expand Down
5 changes: 4 additions & 1 deletion lib/plugins/package/package.js
Expand Up @@ -2,6 +2,7 @@

const BbPromise = require('bluebird');
const path = require('path');
const ServerlessError = require('../../serverless-error');
const cliCommandsSchema = require('../../cli/commands-schema');
const zipService = require('./lib/zipService');
const packageService = require('./lib/packageService');
Expand Down Expand Up @@ -57,7 +58,9 @@ class Package {
this.serverless.cli.log(`Packaging function: ${this.options.function}...`);
return BbPromise.resolve(this.packageFunction(this.options.function));
}
return BbPromise.reject(new Error('Function name must be set'));
return BbPromise.reject(
new ServerlessError('Function name must be set', 'PACKAGE_MISSING_FUNCTION_OPTION')
);
},
};
}
Expand Down
6 changes: 5 additions & 1 deletion lib/plugins/plugin/install.js
Expand Up @@ -5,6 +5,7 @@ const childProcess = BbPromise.promisifyAll(require('child_process'));
const fse = require('fs-extra');
const path = require('path');
const _ = require('lodash');
const ServerlessError = require('../../serverless-error');
const cliCommandsSchema = require('../../cli/commands-schema');
const yamlAstParser = require('../../utils/yamlAstParser');
const fileExists = require('../../utils/fs/fileExists');
Expand Down Expand Up @@ -116,7 +117,10 @@ class PluginInstall {
: newServerlessFileObj.plugins.modules;

if (plugins == null) {
throw new Error('plugins modules property must be present');
throw new ServerlessError(
'plugins modules property must be present',
'PLUGINS_MODULES_MISSING'
);
}

plugins.push(this.options.pluginName);
Expand Down

0 comments on commit c563581

Please sign in to comment.