Skip to content

Commit 236e28e

Browse files
consolidate error handling docs into a single place
1 parent 7119001 commit 236e28e

File tree

4 files changed

+57
-83
lines changed

4 files changed

+57
-83
lines changed

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ The list below contains valuable resources for people interested in contributing
1919
* [Testing strategy](./cli/testing-strategy.md)
2020
* [Cross-OS compatibility](./cli/cross-os-compatibility.md)
2121
* [Troubleshooting](./cli/troubleshooting.md)
22+
* [Error handling principles](cli/error_handling.md)
2223
* [FAQ](./cli/faq.md)
2324

2425
## CLI Kit
2526

2627
The [`@shopify/cli-kit`](https://www.npmjs.com/package/@shopify/cli-kit) NPM package provides utilities to abstract away interactions with the Shopify platform (e.g., authentication, API requests) and ensures experiences are consistent across the board. If you are creating a new plugin or contributing to an existing one, we recommend checking out the following resources:
2728

28-
- [Errors](cli-kit/errors.md)
2929
- [Creating a new command or flag](cli-kit/command-guidelines.md)
3030
- [Content and UI guidelines](cli-kit/ui-kit/guidelines.md)
3131
- [Using UI Kit](cli-kit/ui-kit/readme.md)

docs/cli-kit/errors.md

Lines changed: 0 additions & 80 deletions
This file was deleted.

docs/cli-kit/ui-kit/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ All banners (including errors) are rendered with a width of 2/3 of the full widt
141141

142142
#### Errors
143143

144-
If you're using the `cli-kit`'s `runCLI` function to wrap your CLI, you can throw `AbortError` and let the runner display the exception properly. More on what `AbortError` accepts [here](../errors.md#aborting-the-execution-using-errors).
144+
If you're using the `cli-kit`'s `runCLI` function to wrap your CLI, you can throw `AbortError` and let the runner display the exception properly. More on what `AbortError` accepts [here](../../cli/error_handling.md#aborterror).
145145

146146
If you're using your own custom errors or you're not using `runCLI` then you can use the `renderFatalError` function.
147147
Make sure that your error extends the `cli-kit` class `FatalError` (`AbortError` already does) and pass it to `renderFatalError`

docs/cli/error_handling.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
* These follow from the [General Principles](https://vault.shopify.io/teams/734/pages/Error-Handling-Principles~zEv1.md) document.
44

5+
## Minimize the likelihood of errors happening
6+
7+
**Errors can and will happen**, and handling them gracefully is essential to provide a first-class developer experience.
8+
9+
When designing code, you can leverage tools to **minimize the likelihood of errors happening**. For example, you can leverage Typescript to prevent invalid states from entering the system. It requires additional time to figure out how to model the state, but the investment pays off significantly. If an external state enters the system, for example, a project in the file system, **validate it and return early** if the shape is invalid. It is similar to what web servers do with incoming HTTP requests. We also recommend modeling business logic as a combination of pure functions that are predictable. Functions with the freedom to mutate input state might get mutations introduced in the future that breaks the contract with callers of the function.
10+
11+
Moreover, you should think about scenarios other than the happy path and build for them. There are infinite of those, especially considering that the CLI runs in environments we don't control, so it's essential that you focus on the most obvious ones and don't get too obsessed with trying to anticipate every possible error. **Excellent error management is an iterative process** using unhandled errors as input to inform the improvements.
12+
513
## Global type and handler for fatal errors
614

715
You’ll see in the section below that the CLI currently makes an exception to some of the general principles presented in this doc. This may not be true forever since there are some upcoming projects that will change the tradeoffs.
816

917
### Leverage the `FatalError` hierarchy to short circuit the program and present a custom UX to the user
1018

11-
The CLI defines `FatalError` and several subtypes of `FatalError`. Within the CLI codebase, you can raise any of these errors at any time to **fully halt the program and present a specific UX to the user**.
19+
The CLI defines `FatalError` and several subtypes of `FatalError`. Within the CLI codebase, you can raise any of these subtypes at any time to **fully halt the program and present a specific UX to the user**.
1220

1321
**Principle**: choose the right error type if you need to abort the program and handle the situation in a specific way:
1422

@@ -17,6 +25,8 @@ The CLI defines `FatalError` and several subtypes of `FatalError`. Within the CL
1725
* Use `AbortSilentError` for user-initiated cancellations
1826
* Use `ExternalError` when external commands fail
1927

28+
Please, **don't** use the global `process.exit` and `process.abort` APIs. Also, don't `try {} catch {}` abort errors. If you need to communicate the failure of an operation to the caller (e.g., a 5xx HTTP response), use the result type from the following section.
29+
2030
#### AbortError
2131

2232
Represents expected user/environment issues that require user action
@@ -28,6 +38,20 @@ Represents expected user/environment issues that require user action
2838
* Reported as "expected\_error" to analytics (it’s a user problem, not a Shopify one)
2939
* Sent to Bugsnag as "handled"
3040

41+
You can also pass some next steps to the error constructor to help the user recover from the error. The next steps are displayed after the error message.
42+
43+
For example
44+
```ts
45+
throw new AbortError(
46+
"The project doesn't exist",
47+
undefined,
48+
[
49+
"Make sure the command is executed from a project's directory",
50+
"Run the command again",
51+
]
52+
)
53+
```
54+
3155
#### BugError
3256

3357
Represents unexpected issues that indicate CLI bugs
@@ -114,6 +138,36 @@ The `FatalError` pattern will not work well in an architecture where app develop
114138

115139
However, until then, we get a lot of leverage in the CLI from `FatalError`, so it can continue to exist as a high leverage counter-example to some of our general principles.
116140

141+
## Report a result from a function
142+
There are scenarios where a function needs to inform the caller about the success or failure of the operation. For that, `@shopify/cli-kit` provides a result utility:
143+
144+
```ts
145+
import { FatalError } from "@shopify/cli-kit/node/error"
146+
import {err, ok, Result} from '@shopify/cli-kit/node/result'
147+
148+
class ActionError extends FatalError {}
149+
150+
function action({success}: {success: boolean}): Result<string, ActionError> {
151+
if (success) {
152+
return ok("ok")
153+
} else {
154+
return err(new ActionError("err"))
155+
}
156+
}
157+
158+
// OK result
159+
let result = action({success: true})
160+
result.isErr() // false
161+
result.valueOrBug() // ok
162+
result.mapError((error) => new FatalError("other error"))
163+
164+
// Error result
165+
let result = action({success: false})
166+
result.isErr() // true
167+
result.valueOrBug() // throws!
168+
result.mapError((error) => new FatalError("other error"))
169+
```
170+
117171
## Environmental Issue Detection
118172

119173
The CLI has a unique pattern for classifying non-fatal errors as environment issues through `shouldReportErrorAsUnexpected` and `errorMessageImpliesEnvironmentIssue`.

0 commit comments

Comments
 (0)