Skip to content

Commit 6965b21

Browse files
committed
feat(toolkit-lib): wrap errors from assembly builder into AssemblyError
1 parent b242c23 commit 6965b21

File tree

4 files changed

+107
-18
lines changed

4 files changed

+107
-18
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/toolkit-error.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
const TOOLKIT_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.ToolkitError');
2-
const AUTHENTICATION_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.AuthenticationError');
3-
const ASSEMBLY_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.AssemblyError');
4-
const CONTEXT_PROVIDER_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.ContextProviderError');
1+
import type * as cxapi from '@aws-cdk/cx-api';
2+
3+
const TOOLKIT_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit-lib.ToolkitError');
4+
const AUTHENTICATION_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit-lib.AuthenticationError');
5+
const ASSEMBLY_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit-lib.AssemblyError');
6+
const CONTEXT_PROVIDER_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit-lib.ContextProviderError');
57

68
/**
79
* Represents a general toolkit error in the AWS CDK Toolkit.
@@ -40,19 +42,30 @@ export class ToolkitError extends Error {
4042
*/
4143
public readonly type: string;
4244

45+
/**
46+
* Denotes the source of the error as the toolkit.
47+
*/
48+
public readonly source: 'toolkit' | 'user';
49+
4350
constructor(message: string, type: string = 'toolkit') {
4451
super(message);
4552
Object.setPrototypeOf(this, ToolkitError.prototype);
4653
Object.defineProperty(this, TOOLKIT_ERROR_SYMBOL, { value: true });
4754
this.name = new.target.name;
4855
this.type = type;
56+
this.source = 'toolkit';
4957
}
5058
}
5159

5260
/**
5361
* Represents an authentication-specific error in the AWS CDK Toolkit.
5462
*/
5563
export class AuthenticationError extends ToolkitError {
64+
/**
65+
* Denotes the source of the error as user.
66+
*/
67+
public readonly source = 'user';
68+
5669
constructor(message: string) {
5770
super(message, 'authentication');
5871
Object.setPrototypeOf(this, AuthenticationError.prototype);
@@ -61,20 +74,61 @@ export class AuthenticationError extends ToolkitError {
6174
}
6275

6376
/**
64-
* Represents an authentication-specific error in the AWS CDK Toolkit.
77+
* Represents an error causes by cloud assembly synthesis
78+
*
79+
* This includes errors thrown during app execution, as well as failing annotations.
6580
*/
6681
export class AssemblyError extends ToolkitError {
67-
constructor(message: string) {
82+
/**
83+
* An AssemblyError with an original error as cause
84+
*/
85+
public static fromCause(message: string, error: unknown, stacks?: cxapi.CloudFormationStackArtifact[]): AssemblyError {
86+
return new AssemblyError(message, stacks, error);
87+
}
88+
89+
/**
90+
* An AssemblyError with a list of stacks as cause
91+
*/
92+
public static fromStacks(message: string, stacks?: cxapi.CloudFormationStackArtifact[]): AssemblyError {
93+
return new AssemblyError(message, stacks);
94+
}
95+
96+
/**
97+
* Denotes the source of the error as user.
98+
*/
99+
public readonly source = 'user';
100+
101+
/**
102+
* The stacks that caused the error, if available
103+
*
104+
* The `messages` property of each `cxapi.CloudFormationStackArtifact` will contain the respective errors.
105+
* Absence indicates synthesis didn't fully complete.
106+
*/
107+
public readonly stacks?: cxapi.CloudFormationStackArtifact[];
108+
109+
/**
110+
* The specific original cause of the error, if available
111+
*/
112+
public readonly cause?: unknown;
113+
114+
private constructor(message: string, stacks?: cxapi.CloudFormationStackArtifact[], cause?: unknown) {
68115
super(message, 'assembly');
69116
Object.setPrototypeOf(this, AssemblyError.prototype);
70117
Object.defineProperty(this, ASSEMBLY_ERROR_SYMBOL, { value: true });
118+
this.stacks = stacks;
119+
this.cause = cause;
71120
}
72121
}
73122

74123
/**
75124
* Represents an error originating from a Context Provider
76125
*/
77126
export class ContextProviderError extends ToolkitError {
127+
/**
128+
* Denotes the source of the error as user.
129+
*/
130+
public readonly source = 'user';
131+
78132
constructor(message: string) {
79133
super(message, 'context-provider');
80134
Object.setPrototypeOf(this, ContextProviderError.prototype);

packages/@aws-cdk/tmp-toolkit-helpers/test/api/toolkit-error.test.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,59 @@ import { AssemblyError, AuthenticationError, ContextProviderError, ToolkitError
33
describe('toolkit error', () => {
44
let toolkitError = new ToolkitError('Test toolkit error');
55
let authError = new AuthenticationError('Test authentication error');
6-
let assemblyError = new AssemblyError('Test authentication error');
76
let contextProviderError = new ContextProviderError('Test context provider error');
7+
let assemblyError = AssemblyError.fromStacks('Test authentication error', []);
8+
let assemblyCauseError = AssemblyError.fromCause('Test authentication error', new Error('other error'));
89

910
test('types are correctly assigned', async () => {
1011
expect(toolkitError.type).toBe('toolkit');
1112
expect(authError.type).toBe('authentication');
1213
expect(assemblyError.type).toBe('assembly');
14+
expect(assemblyCauseError.type).toBe('assembly');
1315
expect(contextProviderError.type).toBe('context-provider');
1416
});
1517

1618
test('isToolkitError works', () => {
19+
expect(toolkitError.source).toBe('toolkit');
20+
toolkitError.cause;
21+
1722
expect(ToolkitError.isToolkitError(toolkitError)).toBe(true);
1823
expect(ToolkitError.isToolkitError(authError)).toBe(true);
1924
expect(ToolkitError.isToolkitError(assemblyError)).toBe(true);
25+
expect(ToolkitError.isToolkitError(assemblyCauseError)).toBe(true);
2026
expect(ToolkitError.isToolkitError(contextProviderError)).toBe(true);
2127
});
2228

2329
test('isAuthenticationError works', () => {
30+
expect(authError.source).toBe('user');
31+
2432
expect(ToolkitError.isAuthenticationError(toolkitError)).toBe(false);
2533
expect(ToolkitError.isAuthenticationError(authError)).toBe(true);
2634
});
2735

28-
test('isAssemblyError works', () => {
29-
expect(ToolkitError.isAssemblyError(assemblyError)).toBe(true);
30-
expect(ToolkitError.isAssemblyError(toolkitError)).toBe(false);
31-
expect(ToolkitError.isAssemblyError(authError)).toBe(false);
36+
describe('isAssemblyError works', () => {
37+
test('AssemblyError.fromStacks', () => {
38+
expect(assemblyError.source).toBe('user');
39+
expect(assemblyError.stacks).toStrictEqual([]);
40+
41+
expect(ToolkitError.isAssemblyError(assemblyError)).toBe(true);
42+
expect(ToolkitError.isAssemblyError(toolkitError)).toBe(false);
43+
expect(ToolkitError.isAssemblyError(authError)).toBe(false);
44+
});
45+
46+
test('AssemblyError.fromCause', () => {
47+
expect(assemblyCauseError.source).toBe('user');
48+
expect((assemblyCauseError.cause as any)?.message).toBe('other error');
49+
50+
expect(ToolkitError.isAssemblyError(assemblyCauseError)).toBe(true);
51+
expect(ToolkitError.isAssemblyError(toolkitError)).toBe(false);
52+
expect(ToolkitError.isAssemblyError(authError)).toBe(false);
53+
});
3254
});
3355

3456
test('isContextProviderError works', () => {
57+
expect(contextProviderError.source).toBe('user');
58+
3559
expect(ToolkitError.isContextProviderError(contextProviderError)).toBe(true);
3660
expect(ToolkitError.isContextProviderError(toolkitError)).toBe(false);
3761
expect(ToolkitError.isContextProviderError(authError)).toBe(false);

packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/source-builder.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { assemblyFromDirectory, changeDir, determineOutputDirectory, guessExecut
77
import { ToolkitServices } from '../../../toolkit/private';
88
import { Context, ILock, RWLock, Settings } from '../../aws-cdk';
99
import { CODES, debug } from '../../io/private';
10-
import { ToolkitError } from '../../shared-public';
10+
import { AssemblyError, ToolkitError } from '../../shared-public';
1111
import { AssemblyBuilder } from '../source-builder';
1212

1313
export abstract class CloudAssemblySourceBuilder {
@@ -42,10 +42,21 @@ export abstract class CloudAssemblySourceBuilder {
4242
const env = await prepareDefaultEnvironment(services, { outdir });
4343
const assembly = await changeDir(async () =>
4444
withContext(context.all, env, props.synthOptions ?? {}, async (envWithContext, ctx) =>
45-
withEnv(envWithContext, () => builder({
46-
outdir,
47-
context: ctx,
48-
})),
45+
withEnv(envWithContext, () => {
46+
try {
47+
return builder({
48+
outdir,
49+
context: ctx,
50+
});
51+
} catch (error: unknown) {
52+
// re-throw toolkit errors unchanged
53+
if (ToolkitError.isToolkitError(error)) {
54+
throw error;
55+
}
56+
// otherwise, wrap into an assembly error
57+
throw AssemblyError.fromCause('Assembly builder failed', error);
58+
}
59+
}),
4960
), props.workingDirectory);
5061

5162
if (cxapi.CloudAssembly.isCloudAssembly(assembly)) {

packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,11 @@ export class StackCollection {
336336
}
337337

338338
if (errors && failAt != 'none') {
339-
throw new AssemblyError('Found errors');
339+
throw AssemblyError.fromStacks('Found errors', this.stackArtifacts);
340340
}
341341

342342
if (warnings && failAt === 'warn') {
343-
throw new AssemblyError('Found warnings (--strict mode)');
343+
throw AssemblyError.fromStacks('Found warnings (--strict mode)', this.stackArtifacts);
344344
}
345345
}
346346
}

0 commit comments

Comments
 (0)