Skip to content

Commit

Permalink
feat(core): show token creation stack trace upon resolve error (#2886)
Browse files Browse the repository at this point in the history
Allows implementers of IResolvable to supply a stack trace, which will
automatically be attached to errors thrown during resolution.

Implement this for lazy and intrinsic tokens.
  • Loading branch information
Elad Ben-Israel authored Jun 18, 2019
1 parent c3667d7 commit f4c8dcd
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 14 deletions.
8 changes: 7 additions & 1 deletion packages/@aws-cdk/cdk/lib/lazy.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createStackTrace } from './private/stack-trace';
import { IResolvable, IResolveContext } from "./resolvable";
import { Token } from "./token";

Expand Down Expand Up @@ -119,8 +120,13 @@ export class Lazy {
}

abstract class LazyBase implements IResolvable {
public abstract resolve(context: IResolveContext): any;
public readonly creationStack: string[];

constructor() {
this.creationStack = createStackTrace();
}

public abstract resolve(context: IResolveContext): any;
public toString() {
return Token.asString(this);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/cdk/lib/private/intrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class Intrinsic implements IResolvable {
/**
* The captured stack trace which represents the location in which this token was created.
*/
protected readonly trace: string[];
public readonly creationStack: string[];

private readonly value: any;

Expand All @@ -25,7 +25,7 @@ export class Intrinsic implements IResolvable {
throw new Error(`Argument to Intrinsic must be a plain value object, got ${value}`);
}

this.trace = createStackTrace();
this.creationStack = createStackTrace();
this.value = value;
}

Expand Down Expand Up @@ -68,7 +68,7 @@ export class Intrinsic implements IResolvable {
* @param message Error message
*/
protected newError(message: string): any {
return new Error(`${message}\nToken created:\n at ${this.trace.join('\n at ')}\nError thrown:`);
return new Error(`${message}\nToken created:\n at ${this.creationStack.join('\n at ')}\nError thrown:`);
}
}

Expand Down
32 changes: 23 additions & 9 deletions packages/@aws-cdk/cdk/lib/resolvable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export interface IResolveContext {
* Tokens are special objects that participate in synthesis.
*/
export interface IResolvable {
/**
* The creation stack of this resolvable which will be appended to errors
* thrown during resolution.
*/
readonly creationStack?: string[];

/**
* Produce the Token's value at resolution time
*/
Expand Down Expand Up @@ -113,14 +119,22 @@ export class DefaultTokenResolver implements ITokenResolver {
* then finally post-process it.
*/
public resolveToken(t: IResolvable, context: IResolveContext, postProcessor: IPostProcessor) {
let resolved = t.resolve(context);

// The token might have returned more values that need resolving, recurse
resolved = context.resolve(resolved);

resolved = postProcessor.postProcess(resolved, context);

return resolved;
try {
let resolved = t.resolve(context);

// The token might have returned more values that need resolving, recurse
resolved = context.resolve(resolved);
resolved = postProcessor.postProcess(resolved, context);
return resolved;
} catch (e) {
let message = `Resolution error: ${e.message}.`;
if (t.creationStack && t.creationStack.length > 0) {
message += `\nObject creation stack:\n at ${t.creationStack.join('\n at ')}`;
}

e.message = message;
throw e;
}
}

/**
Expand All @@ -145,4 +159,4 @@ export class DefaultTokenResolver implements ITokenResolver {

return fragments.mapTokens({ mapToken: context.resolve }).firstValue;
}
}
}
19 changes: 18 additions & 1 deletion packages/@aws-cdk/cdk/test/test.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ export = {
function fn2() {
class ExposeTrace extends Intrinsic {
public get creationTrace() {
return this.trace;
return this.creationStack;
}
}

Expand Down Expand Up @@ -582,6 +582,23 @@ export = {

return tests;
})(),

'creation stack is attached to errors emitted during resolve'(test: Test) {
function showMeInTheStackTrace() {
return Lazy.stringValue({ produce: () => { throw new Error('fooError'); } });
}

const x = showMeInTheStackTrace();
let message;
try {
resolve(x);
} catch (e) {
message = e.message;
}

test.ok(message && message.includes('showMeInTheStackTrace'));
test.done();
}
};

class Promise2 implements IResolvable {
Expand Down

0 comments on commit f4c8dcd

Please sign in to comment.