Skip to content

Commit e6a796c

Browse files
feat(errors): implement consistent error codes and exit codes. fixes #215 (#428)
1 parent 4bfec23 commit e6a796c

File tree

83 files changed

+631
-442
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+631
-442
lines changed

.eslintrc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@
1010
"imports": "always-multiline",
1111
"exports": "always-multiline",
1212
"functions": "always-multiline"
13-
}]
14-
}
13+
}],
14+
"aio-cli-plugin-cloudmanager-base-class": "error",
15+
"aio-cli-plugin-cloudmanager-error-usage": "error",
16+
"aio-cli-plugin-cloudmanager-stop-usage": "error"
17+
},
18+
"overrides": [
19+
{
20+
"files" : ["src/base-command.js"],
21+
"rules" : {
22+
"aio-cli-plugin-cloudmanager-base-class": "off"
23+
}
24+
},
25+
{
26+
"files" : ["test/**/*.js"],
27+
"rules" : {
28+
"aio-cli-plugin-cloudmanager-error-usage": "off"
29+
}
30+
}
31+
]
1532
}

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ All submissions should come in the form of pull requests and need to be reviewed
1818

1919
Please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when submitting a pull request. Following the requirements for semantic releases (as described in the next section), each pull request should contain a single change and be comprised of a single commit.
2020

21+
## Error Handling
22+
23+
In order to ensure proper error handling, individual commands should generally *not* handle errors themselves. Commands should throw errors and allow them to be caught by the `catch` method in `BaseCommand`. There may be exceptions specifically around errors that are non-fatal. Thrown errors should be defined in either `ValidationErrors` or `ConfigurationErrors`.
24+
25+
Hooks should work in a similar fashion -- see the prerun `check-ims-context-config` hook as a point of reference.
26+
2127
## Commits and Releasing
2228

2329
Commits (generally via merged pull requests) to the `main` branch of this repository will automatically generate [semantically versioned releases](https://github.com/semantic-release). To accomplish this, commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) syntax, specifically:

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ $ aio config:set cloudmanager_environmentid 7
150150

151151
> This only works for commands where the environmentId is the **first** argument.
152152
153+
## Exit Codes
154+
155+
Primarily for scripting application purposes, the following exit codes are used:
156+
157+
1 - A generic error has occurred
158+
2 - A configuration error has occurred
159+
3 - A validation error with the supplied flags or arguments has occurred
160+
30 - An error emanating from the Cloud Manager SDK has occurred
161+
153162
## Reporting Issues
154163

155164
In general, issues with this plugin should be reported in this project using GitHub issues using one of the provided issue templates.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
module.exports = {
13+
create: function (context) {
14+
return {
15+
ClassDeclaration: (node) => {
16+
const superClass = node.superClass && node.superClass.name
17+
if (superClass === 'Command') {
18+
context.report({
19+
node: node,
20+
message: 'Class {{ className }} extends Command, not BaseCommand.',
21+
data: {
22+
className: node.id.name,
23+
},
24+
})
25+
}
26+
},
27+
}
28+
},
29+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
module.exports = {
13+
create: function (context) {
14+
return {
15+
CallExpression: (node) => {
16+
if (node.callee.object && node.callee.object.type === 'ThisExpression' && node.callee.property && node.callee.property.name === 'error') {
17+
if (node.parent && node.parent.parent && node.parent.parent.parent && node.parent.parent.parent.type === 'CatchClause') {
18+
context.report({
19+
node: node,
20+
message: 'this.error should not be used in catch blocks.',
21+
})
22+
} else if (node.arguments.length !== 2) {
23+
context.report({
24+
node: node,
25+
message: 'this.error must be called with two arguments.',
26+
})
27+
} else {
28+
const secondArgument = node.arguments[1]
29+
if (secondArgument.type !== 'ObjectExpression') {
30+
context.report({
31+
node: node,
32+
message: 'second argument to this.error must be an object.',
33+
})
34+
} else {
35+
const propertyNames = secondArgument.properties.map(property => property.key.name)
36+
if (!propertyNames.includes('code')) {
37+
context.report({
38+
node: node,
39+
message: 'second argument to this.error must contain an error code.',
40+
})
41+
}
42+
if (!propertyNames.includes('exit')) {
43+
context.report({
44+
node: node,
45+
message: 'second argument to this.error must contain an exit code.',
46+
})
47+
}
48+
}
49+
}
50+
}
51+
},
52+
NewExpression: (node) => {
53+
if (node.callee.name === 'Error') {
54+
context.report({
55+
node: node,
56+
message: 'Error constructor should not be used. ValidationErrors or ConfigurationErrors should be used instead.',
57+
})
58+
}
59+
},
60+
}
61+
},
62+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
module.exports = {
13+
create: function (context) {
14+
return {
15+
CallExpression: (node) => {
16+
if (node.callee.object && node.callee.object.type === 'MemberExpression' && node.callee.object.property.name === 'action' &&
17+
node.callee.property && node.callee.property.name === 'stop') {
18+
if (node.parent && node.parent.parent && node.parent.parent.parent && node.parent.parent.parent.type === 'CatchClause') {
19+
context.report({
20+
node: node,
21+
message: 'cli.action.stop should not be used in catch blocks.',
22+
})
23+
}
24+
}
25+
},
26+
}
27+
},
28+
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"dependencies": {
88
"@adobe/aio-lib-cloudmanager": "^1.3.0",
99
"@adobe/aio-lib-core-config": "^2.0.0",
10+
"@adobe/aio-lib-core-errors": "^3.0.1",
1011
"@adobe/aio-lib-core-logging": "^1.2.0",
1112
"@adobe/aio-lib-env": "^1.1.0",
1213
"@adobe/aio-lib-ims": "^4.1.1",
@@ -119,8 +120,8 @@
119120
},
120121
"scripts": {
121122
"posttest": "npm run lint",
122-
"lint": "eslint src test e2e",
123-
"lint-fix": "eslint src test e2e --fix",
123+
"lint": "eslint src test e2e --rulesdir eslint_rules",
124+
"lint-fix": "eslint src test e2e --fix --rulesdir eslint_rules",
124125
"test": "npm run unit-tests",
125126
"unit-tests": "jest --ci",
126127
"prepack": "oclif-dev manifest && oclif-dev readme",

src/ConfigurationErrors.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
const { ErrorWrapper, createUpdater } = require('@adobe/aio-lib-core-errors').AioCoreSDKErrorWrapper
13+
14+
const codes = {}
15+
const messages = new Map()
16+
17+
/**
18+
* Create an Updater for the Error wrapper
19+
*
20+
* @ignore
21+
*/
22+
const Updater = createUpdater(
23+
// object that stores the error classes (to be exported)
24+
codes,
25+
// Map that stores the error strings (to be exported)
26+
messages,
27+
)
28+
29+
/**
30+
* Provides a wrapper to easily create classes of a certain name, and values
31+
*
32+
* @ignore
33+
*/
34+
const E = ErrorWrapper(
35+
// The class name for your SDK Error. Your Error objects will be these objects
36+
'CloudManagerCLIConfigurationError',
37+
// The name of your SDK. This will be a property in your Error objects
38+
'CloudManagerCLI',
39+
// the object returned from the CreateUpdater call above
40+
Updater,
41+
// the base class that your Error class is extending. AioCoreSDKError is the default
42+
/* AioCoreSDKError, */
43+
)
44+
45+
module.exports = {
46+
codes,
47+
messages,
48+
}
49+
50+
// Define your error codes with the wrapper
51+
E('CLI_ONLY_COMMAND', 'This command is only intended to be used with a user token, not a service account. The org id for a service account must be provided in the service account configuration.')
52+
E('NO_CM_ORGS', 'No Cloud Manager authorized organizations found.')
53+
E('NO_IMS_CONTEXT', 'Unable to find IMS context %s.')
54+
E('CLI_AUTH_NO_ORG', 'The CLI has been authenticated, but no organization has been selected. To select an organization, run "aio cloudmanager:org:select". Alternatively, define the IMS context configuration %s with a service account.')
55+
E('NO_DEFAULT_IMS_CONTEXT', 'There is no IMS context configuration defined for %s. Either define this context configuration or authenticate using "aio auth:login" and select an organization using "aio cloudmanager:org:select".')
56+
E('IMS_CONTEXT_MISSING_FIELDS', 'One or more of the required fields in %s were not set. Missing keys were %s.')
57+
E('IMS_CONTEXT_MISSING_METASCOPE', 'The configuration %s is missing the required metascope %s.')
58+
E('CLI_AUTH_EXPLICIT_NO_AUTH', 'cli context explicitly enabled, but not authenticated. You must run "aio auth:login" first.')
59+
E('CLI_AUTH_EXPLICIT_NO_ORG', 'cli context explicitly enabled but no org id specified. Configure using either "cloudmanager_orgid" or by running "aio cloudmanager:org:select"')

src/ValidationErrors.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
const { ErrorWrapper, createUpdater } = require('@adobe/aio-lib-core-errors').AioCoreSDKErrorWrapper
13+
14+
const codes = {}
15+
const messages = new Map()
16+
17+
/**
18+
* Create an Updater for the Error wrapper
19+
*
20+
* @ignore
21+
*/
22+
const Updater = createUpdater(
23+
// object that stores the error classes (to be exported)
24+
codes,
25+
// Map that stores the error strings (to be exported)
26+
messages,
27+
)
28+
29+
/**
30+
* Provides a wrapper to easily create classes of a certain name, and values
31+
*
32+
* @ignore
33+
*/
34+
const E = ErrorWrapper(
35+
// The class name for your SDK Error. Your Error objects will be these objects
36+
'CloudManagerCLIValidationError',
37+
// The name of your SDK. This will be a property in your Error objects
38+
'CloudManagerCLI',
39+
// the object returned from the CreateUpdater call above
40+
Updater,
41+
// the base class that your Error class is extending. AioCoreSDKError is the default
42+
/* AioCoreSDKError, */
43+
)
44+
45+
module.exports = {
46+
codes,
47+
messages,
48+
}
49+
50+
// Define your error codes with the wrapper
51+
E('INTERNAL_VARIABLE_USAGE', 'The variable name %s is reserved for internal usage and will be ignored.')
52+
E('IP_ALLOWLIST_NOT_FOUND', 'Could not find IP Allowlist with id %s in program id %s.')
53+
E('VARIABLES_JSON_PARSE_ERROR', 'Unable to parse variables from provided data.')
54+
E('VARIABLES_JSON_NOT_ARRAY', 'Provided variables input was not an array.')
55+
E('BOTH_BRANCH_AND_TAG_PROVIDED', 'Both branch and tag cannot be specified.')
56+
E('MISSING_PROGRAM_ID', 'Program ID must be specified either as --programId flag or through cloudmanager_programid config value.')
57+
E('MISSING_METRICS', 'Metrics for action %s on execution %s could not be found.')
58+
E('INVALID_TAG_SYNTAX', 'tag flag should not be specified with "refs/tags/" prefix. Value provided was %s')
59+
E('JSON_PARSE_NUMBER', 'parsed flag value as a number')
60+
E('BLANK_VARIABLE_VALUE', 'Blank variable values are not allowed. Use the proper flag if you intend to delete a variable.')
61+
E('MALFORMED_NAME_VALUE_PAIR', 'Please provide correct values for flags')

src/base-command.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
Copyright 2021 Adobe. All rights reserved.
3+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License. You may obtain a copy
5+
of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
Unless required by applicable law or agreed to in writing, software distributed under
7+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8+
OF ANY KIND, either express or implied. See the License for the specific language
9+
governing permissions and limitations under the License.
10+
*/
11+
12+
const { Command } = require('@oclif/command')
13+
const { handleError } = require('./cloudmanager-helpers')
14+
15+
class BaseCommand extends Command {
16+
async catch (err) {
17+
handleError(err, this.error)
18+
}
19+
}
20+
21+
module.exports = BaseCommand

0 commit comments

Comments
 (0)