Skip to content

Commit 2f0caf4

Browse files
authored
chore(toolkit-lib): fix code registry is disconnected from usage (#191)
Previously the declared `level` and payload interfaces were only loosely coupled to actual messages being created. This caused a number of issues with incorrectly documented levels and payload interfaces. Instead of the previous approach, the code registry is now providing a strongly typed "message builder" function specific to every registered code. This function uses generics, so it can correctly ensure at build time that payloads are as expected. This is the first PR of this series to keep the diff small. Next steps will be to make the code registry available to the CLI package and to remove the old helpers for creating messages. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent 01acccf commit 2f0caf4

File tree

28 files changed

+272
-222
lines changed

28 files changed

+272
-222
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './io';
12
export * from './toolkit-error';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './io-host';
2+
export * from './io-message';
3+
export * from './toolkit-action';

packages/@aws-cdk/toolkit-lib/lib/api/io/io-host.ts renamed to packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/io-host.ts

File renamed without changes.

packages/@aws-cdk/toolkit-lib/lib/api/io/io-message.ts renamed to packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/io-message.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ToolkitAction } from '../../toolkit';
1+
import { ToolkitAction } from './toolkit-action';
22

33
/**
44
* The reporting level of the message.
@@ -7,7 +7,7 @@ import { ToolkitAction } from '../../toolkit';
77
export type IoMessageLevel = 'error'| 'result' | 'warn' | 'info' | 'debug' | 'trace';
88

99
/**
10-
* A valid message code. See https://github.com/aws/aws-cdk-cli/blob/main/packages/%40aws-cdk/toolkit-lib/CODE_REGISTRY.md
10+
* A valid message code.
1111
*/
1212
export type IoMessageCode = `CDK_${string}_${'E' | 'W' | 'I'}${number}${number}${number}${number}`;
1313

@@ -21,7 +21,10 @@ export interface IoMessage<T> {
2121
readonly time: Date;
2222

2323
/**
24-
* The log level of the message.
24+
* The recommended log level of the message.
25+
*
26+
* This is an indicative level and should not be used to explicitly match messages, instead match the `code`.
27+
* The level of a message may change without notice.
2528
*/
2629
readonly level: IoMessageLevel;
2730

@@ -45,6 +48,8 @@ export interface IoMessage<T> {
4548
* 'CDK_TOOLKIT_E0002' // valid: specific toolkit error message
4649
* 'CDK_SDK_W0023' // valid: specific sdk warning message
4750
* ```
51+
*
52+
* @see https://github.com/aws/aws-cdk-cli/blob/main/packages/%40aws-cdk/toolkit-lib/CODE_REGISTRY.md
4853
*/
4954
readonly code: IoMessageCode;
5055

@@ -60,6 +65,9 @@ export interface IoMessage<T> {
6065
readonly data?: T;
6166
}
6267

68+
/**
69+
* An IO request emitted.
70+
*/
6371
export interface IoRequest<T, U> extends IoMessage<T> {
6472
/**
6573
* The default response that will be used if no data is returned.

packages/@aws-cdk/toolkit-lib/lib/api/io/private/types.ts renamed to packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/action-aware.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
11
import { IIoHost } from '../io-host';
22
import { IoMessage, IoRequest } from '../io-message';
3-
4-
/**
5-
* Valid reporting categories for messages.
6-
*/
7-
export type IoMessageCodeCategory = 'TOOLKIT' | 'SDK' | 'ASSETS' | 'ASSEMBLY';
8-
9-
/**
10-
* Code level matching the reporting level.
11-
*/
12-
export type IoCodeLevel = 'E' | 'W' | 'I';
13-
14-
/**
15-
* A message code at a specific level
16-
*/
17-
export type IoMessageSpecificCode<L extends IoCodeLevel> = `CDK_${IoMessageCodeCategory}_${L}${number}${number}${number}${number}`;
3+
import { ToolkitAction } from '../toolkit-action';
184

195
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
206
export type SimplifiedMessage<T> = Pick<IoMessage<T>, 'level' | 'code' | 'message' | 'data'>;
@@ -28,3 +14,24 @@ export interface ActionAwareIoHost extends IIoHost {
2814
notify<T>(msg: ActionLessMessage<T>): Promise<void>;
2915
requestResponse<T, U>(msg: ActionLessRequest<T, U>): Promise<U>;
3016
}
17+
18+
/**
19+
* An IoHost wrapper that adds the given action to an actionless message before
20+
* sending the message to the given IoHost
21+
*/
22+
export function withAction(ioHost: IIoHost, action: ToolkitAction): ActionAwareIoHost {
23+
return {
24+
notify: async <T>(msg: Omit<IoMessage<T>, 'action'>) => {
25+
await ioHost.notify({
26+
...msg,
27+
action,
28+
});
29+
},
30+
requestResponse: async <T, U>(msg: Omit<IoRequest<T, U>, 'action'>) => {
31+
return ioHost.requestResponse({
32+
...msg,
33+
action,
34+
});
35+
},
36+
};
37+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './action-aware';
2+
export * from './message-maker';
3+
export * from './types';
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { IoMessageCode, IoMessageLevel } from '../io-message';
2+
import { ActionLessMessage } from './action-aware';
3+
4+
/**
5+
* Information for each IO Message Code.
6+
*/
7+
interface CodeInfo {
8+
/**
9+
* The message code.
10+
*/
11+
code: IoMessageCode;
12+
13+
/**
14+
* A brief description of the meaning of this IO Message.
15+
*/
16+
description: string;
17+
18+
/**
19+
* The name of the payload interface, if applicable.
20+
* Some Io Messages include a payload, with a specific interface. The name of
21+
* the interface is specified here so that it can be linked with the message
22+
* when documentation is generated.
23+
*
24+
* The interface _must_ be exposed directly from toolkit-lib, so that it will
25+
* have a documentation page generated (that can be linked to).
26+
*/
27+
interface?: string;
28+
}
29+
30+
/**
31+
* Information for each IO Message
32+
*/
33+
interface MessageInfo extends CodeInfo {
34+
/**
35+
* The message level
36+
*/
37+
level: IoMessageLevel;
38+
}
39+
40+
/**
41+
* An interface that can produce messages for a specific code.
42+
*/
43+
export interface IoMessageMaker<T> extends MessageInfo {
44+
/**
45+
* Create a message for this code, with or without payload.
46+
*/
47+
msg: [T] extends [never] ? (message: string) => ActionLessMessage<never> : (message: string, data: T) => ActionLessMessage<T>;
48+
}
49+
50+
/**
51+
* Produce an IoMessageMaker for the provided level and code info.
52+
*/
53+
function generic<T = never>(level: IoMessageLevel, details: CodeInfo): IoMessageMaker<T> {
54+
const msg = (message: string, data: T) => ({
55+
time: new Date(),
56+
level,
57+
code: details.code,
58+
message: message,
59+
data: data,
60+
} as ActionLessMessage<T>);
61+
62+
return {
63+
...details,
64+
level,
65+
msg: msg as any,
66+
};
67+
}
68+
69+
// Create `IoMessageMaker`s for a given level and type check that calls with payload are using the correct interface
70+
type CodeInfoMaybeInterface<T> = [T] extends [never] ? Omit<CodeInfo, 'interface'> : Required<CodeInfo>;
71+
72+
export const trace = <T = never>(details: CodeInfoMaybeInterface<T>) => generic<T>('trace', details);
73+
export const debug = <T = never>(details: CodeInfoMaybeInterface<T>) => generic<T>('debug', details);
74+
export const info = <T = never>(details: CodeInfoMaybeInterface<T>) => generic<T>('info', details);
75+
export const warn = <T = never>(details: CodeInfoMaybeInterface<T>) => generic<T>('warn', details);
76+
export const error = <T = never>(details: CodeInfoMaybeInterface<T>) => generic<T>('error', details);
77+
export const result = <T extends object>(details: Required<CodeInfo>) => generic<T extends object ? T : never>('result', details);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Valid reporting categories for messages.
3+
*/
4+
export type IoMessageCodeCategory = 'TOOLKIT' | 'SDK' | 'ASSETS' | 'ASSEMBLY';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* The current action being performed by the CLI. 'none' represents the absence of an action.
3+
*/
4+
export type ToolkitAction =
5+
| 'assembly'
6+
| 'bootstrap'
7+
| 'synth'
8+
| 'list'
9+
| 'diff'
10+
| 'deploy'
11+
| 'rollback'
12+
| 'watch'
13+
| 'destroy'
14+
| 'doctor'
15+
| 'gc'
16+
| 'import'
17+
| 'metadata'
18+
| 'init'
19+
| 'migrate';

packages/@aws-cdk/toolkit-lib/CODE_REGISTRY.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
| Code | Description | Level | Data Interface |
44
|------|-------------|-------|----------------|
5-
| CDK_TOOLKIT_I1000 | Provides synthesis times. | info | n/a |
6-
| CDK_TOOLKIT_I1901 | Provides stack data | result | [StackData](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/StackData.html) |
5+
| CDK_TOOLKIT_I1000 | Provides synthesis times. | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
6+
| CDK_TOOLKIT_I1901 | Provides stack data | result | [StackAndAssemblyData](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/StackAndAssemblyData.html) |
77
| CDK_TOOLKIT_I1902 | Successfully deployed stacks | result | [AssemblyData](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/AssemblyData.html) |
8-
| CDK_TOOLKIT_I2901 | Provides details on the selected stacks and their dependencies | result | n/a |
8+
| CDK_TOOLKIT_I2901 | Provides details on the selected stacks and their dependencies | result | [StackDetailsPayload](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/StackDetailsPayload.html) |
99
| CDK_TOOLKIT_E3900 | Resource import failed | error | n/a |
10-
| CDK_TOOLKIT_I5000 | Provides deployment times | info | n/a |
10+
| CDK_TOOLKIT_I5000 | Provides deployment times | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
1111
| CDK_TOOLKIT_I5001 | Provides total time in deploy action, including synth and rollback | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
1212
| CDK_TOOLKIT_I5002 | Provides time for resource migration | info | n/a |
1313
| CDK_TOOLKIT_I5031 | Informs about any log groups that are traced as part of the deployment | info | n/a |
@@ -19,20 +19,20 @@
1919
| CDK_TOOLKIT_I5900 | Deployment results on success | result | [SuccessfulDeployStackResult](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/SuccessfulDeployStackResult.html) |
2020
| CDK_TOOLKIT_E5001 | No stacks found | error | n/a |
2121
| CDK_TOOLKIT_E5500 | Stack Monitoring error | error | [ErrorPayload](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/ErrorPayload.html) |
22-
| CDK_TOOLKIT_I6000 | Provides rollback times | info | n/a |
22+
| CDK_TOOLKIT_I6000 | Provides rollback times | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
2323
| CDK_TOOLKIT_E6001 | No stacks found | error | n/a |
2424
| CDK_TOOLKIT_E6900 | Rollback failed | error | n/a |
25-
| CDK_TOOLKIT_I7000 | Provides destroy times | info | n/a |
25+
| CDK_TOOLKIT_I7000 | Provides destroy times | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
2626
| CDK_TOOLKIT_I7010 | Confirm destroy stacks | info | n/a |
2727
| CDK_TOOLKIT_E7010 | Action was aborted due to negative confirmation of request | error | n/a |
2828
| CDK_TOOLKIT_E7900 | Stack deletion failed | error | n/a |
29-
| CDK_TOOLKIT_I9000 | Provides bootstrap times | info | n/a |
29+
| CDK_TOOLKIT_I9000 | Provides bootstrap times | info | [Duration](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/Duration.html) |
3030
| CDK_TOOLKIT_I9900 | Bootstrap results on success | info | n/a |
3131
| CDK_TOOLKIT_E9900 | Bootstrap failed | error | n/a |
32-
| CDK_ASSEMBLY_I0042 | Writing updated context | debug | n/a |
33-
| CDK_ASSEMBLY_I0241 | Fetching missing context | debug | n/a |
32+
| CDK_ASSEMBLY_I0042 | Writing updated context | debug | [UpdatedContext](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/UpdatedContext.html) |
33+
| CDK_ASSEMBLY_I0241 | Fetching missing context | debug | [MissingContext](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/MissingContext.html) |
3434
| CDK_ASSEMBLY_I1000 | Cloud assembly output starts | debug | n/a |
3535
| CDK_ASSEMBLY_I1001 | Output lines emitted by the cloud assembly to stdout | info | n/a |
3636
| CDK_ASSEMBLY_E1002 | Output lines emitted by the cloud assembly to stderr | error | n/a |
3737
| CDK_ASSEMBLY_I1003 | Cloud assembly output finished | info | n/a |
38-
| CDK_ASSEMBLY_E1111 | Incompatible CDK CLI version. Upgrade needed. | error | n/a |
38+
| CDK_ASSEMBLY_E1111 | Incompatible CDK CLI version. Upgrade needed. | error | [ErrorPayload](https://docs.aws.amazon.com/cdk/api/toolkit-lib/interfaces/ErrorPayload.html) |

0 commit comments

Comments
 (0)