Skip to content

Commit

Permalink
errors
Browse files Browse the repository at this point in the history
  • Loading branch information
mrgrain committed Nov 18, 2024
1 parent 55d2c3f commit 9f9403f
Showing 1 changed file with 110 additions and 14 deletions.
124 changes: 110 additions & 14 deletions text/0300-programmatic-toolkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The package provides implementations for the following actions:

```ts
// Create a [Programmatic Toolkit]
const cdk = new Toolkit({
await using cdk = new Toolkit({
ioHost: new CloudWatchLogsIoHost('my-log-stream'), // write all output to CW logs
// awsSdkProvider: new DefaultAwsSdk(), // optional
});
Expand Down Expand Up @@ -80,8 +80,8 @@ interface IoMessage<T> {
data?: T;
}

interface IoRequest<T> extends IoMessage<T> {
defaultResponse: T;
interface IoRequest<T, U> extends IoMessage<T> {
defaultResponse: U;
}
```

Expand Down Expand Up @@ -130,7 +130,7 @@ interface IIoHost {
* If the host does not return a response the suggested
* default response from the input message will be used.
*/
async requestResponse(msg: IoRequest<T>): T | undefined;
async requestResponse(msg: IoRequest<T,U>): U | undefined;
}
```

Expand Down Expand Up @@ -223,14 +223,110 @@ It returns a list of stack deployments.

#### Errors

<!--
Integrators need access to FR3,Structured errors from both the lifecycle actions and synth-time errors in a user’s app. During lifecycle actions, errors will be thrown in the usual manner. How we provide access to synth-time errors will be covered in the next section.
All errors are thrown as exceptions and the integrator must be prepared to handle them.
Expected errors are structured data and extend the `IoMessage` interface.

All errors will be represented as structured data. -->
```ts
interface ToolkitError extends IoMessage {
source: 'toolkit' | 'user';
type: 'authentication' | 'cloudformation' | 'validation' ...;
fqn?: string;
}
```

<!-- Synth-time Errors
The Toolkit library must allow for synthesis-time errors from the user's application code to be passed to the Event Host. When user code is executed in a child process, error objects are lost. We cannot avoid this for jsii languages where. Therefore true error propagation will only be available to TypeScript consumers. An implementor can catch these errors when executing the user’s app code, like cli-lib-alpha package does today: -->
The toolkit might throw other exceptions.
These are bugs and you should report them by [raising an issue](https://github.com/aws/aws-cdk/issues/new?assignees=&labels=bug%2Cneeds-triage&projects=&template=bug-report.yml&title=%28toolkit%29%3A+Untyped+Exception+%28short+description%29).
To assist integrators with detecting the type of error, the following helper methods are available.
Even though errors are typed, you should not rely on `instanceof` checks because it can behave unexpectedly when working with multiple copies of the same package. [TODO: insert reference link]

```ts
try {
await cdk.deploy(...);
} catch (e: unknown) {
if (ToolkitError.isToolkitError(error)) {
// handle toolkit errors
} else if (ToolkitError.isUserError(e)) {
// handle user code errors
}

if (ToolkitError.isCdkError(e)) {
// handle any cdk errors
}
}
```

##### Recoverable Errors [TODO: Naming. All errors in JS are recoverable. Ruby calls this rescue/retry]

[Alternative: Make retry a boolean]
[Alternative: Limit to exactly 1 rescue attempt]

While most errors are terminal, it's possible to recover from some.
For example if an authentication token is expired, an integrator may prompt the user to re-authenticate via a browser login, before continuing with the action.
Formally, recoverable errors allow the integrator to return to the regular program flow after an issue has been addressed.
Recoverable errors are rare as they require special programming and are not always possible.

In the [Programmatic Toolkit], recoverable errors are implemented as a special type of request.
By default, errors will not be attempted to recover.

```ts
interface RecoverableError<T extends { attempt: number }, U extends { retry: number }> extends IoRequest<T, U> {
level: 'error';
defaultResponse: {
retry: number; // the budget for retries
// ... other response values
};
data: {
attempt: number;
}
}
```

When `IoHost.requestResponse()` is called with a recoverable error, the integrator may choose to return `retry: 1` (or any number) to indicate the block should be retried.
This value is called the retry budget.
If the block fails again with the same error, the `IoHost` will be called again until the retry budget is used up.
Retries are not guaranteed and integrators must handle the case where a requested retry is not executed.
Once the retry budged is depleted, the error from the final attempt will be raised as an exception.

##### Synth-time Errors

There is a subtle semantic difference between errors that originate from the [Programmatic Toolkit] and from a Cloud Executable, i.e. from a user's app:
Errors from the [Programmatic Toolkit] are typically misconfigurations and maybe fixable by an integrator,
while errors from a Cloud Executable are usually code problems and only fixable by the user.

The [Programmatic Toolkit] emits all synth-time errors as recoverable errors.
When implementing a custom Cloud Executable in code, you may prefer to handle synth-time errors directly in code:

```ts
class MyCloudExecutable implements ICloudExecutable {
async exec(context: Record<string, any>): cxapi.CloudAssembly {
try {
const app = new cdk.App({ context });
const stack = new cdk.Stack(app);
return app.synth();
} catch(e: unknown) {
// handle errors here
}
}
}
```

#### Dispose

The `Toolkit` class implements an `AsyncDisposable` Resource according to the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) feature in ECMAScript.
This means that any resources used by the [Programmatic Toolkit] are automatically cleaned-up.
`AsyncDisposable` guarantees this clean-up even in case of an uncaught exception.

You may also call the `dispose()` method directly.
We recommend to do this inside a `finally` statement to guarantee execution.

```ts
const cdk = new Toolkit();
try {
cdk.deploy(...);
} finally {
await cdk.dispose();
}
```

---

Expand Down Expand Up @@ -394,7 +490,7 @@ The project will be delivered incrementally over multiple months.
We start with core functionalities and the most commonly used actions.
A more detailed project plan will be provided when it is available.

#### Milestone 1
#### Milestone 1 // 8 two-dev-weeks

Covers foundational features and the most commonly used actions.

Expand All @@ -406,7 +502,7 @@ Covers foundational features and the most commonly used actions.
* implement options for cli-lib
* generate options from toolkit interface

#### Milestone 2
#### Milestone 2 // 8 two-dev-weeks

Added support for actions required to cover the complete lifecycle of a CDK app.
Typed returns to increase support for even more complex scenarios.
Expand All @@ -418,7 +514,7 @@ Use [Programmatic Toolkit] in `integ-runner` and integration tests.
* integration Tests
* integ-runner

#### Milestone 3
#### Milestone 3 // 4 two-dev-weeks

GA. Added support for operational actions and release of the jsii toolkit.

Expand All @@ -427,7 +523,7 @@ GA. Added support for operational actions and release of the jsii toolkit.
* events for above actions
* new jsii package, removal of cli-lib-alpha

#### Milestone 4
#### Milestone 4 // 4 two-dev-weeks

GA. Added support for highly interactive actions.

Expand Down Expand Up @@ -568,4 +664,4 @@ to spawn cli commands directly.

It will improve the performance and simplify use cases like running the AWS CDK
inside an AWS Lambda Function. As a jsii package, the construct will be
available to all jsii target languages.
available to all jsii target languages.a

0 comments on commit 9f9403f

Please sign in to comment.