Skip to content

Commit

Permalink
feat(tokens): enable type coercion
Browse files Browse the repository at this point in the history
relax restrictions on input types for Token.toXxx in order to allow
flexible type coercion.

this may be needed in situations where users want to force a token
typed as one type to be represented as another type and generally
allow tokens to be used as "type-system escape hatches".

Previously, this did not work:

    const port = new Token({ "Fn::GetAtt": [ "ResourceId", "Port" ] }).toString(); 
    new TcpPort(new Token(port).toNumber());

Also, this did not work:

    const port = new Token({ "Fn::GetAtt": [ "ResourceId", "Port" ]}).toNumber();

Fixes #2679
  • Loading branch information
Elad Ben-Israel committed May 30, 2019
1 parent c52bcfc commit 5b0730f
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 16 deletions.
26 changes: 12 additions & 14 deletions packages/@aws-cdk/cdk/lib/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ export class Token {
* on the string.
*/
public toString(): string {
const valueType = typeof this.valueOrFunction;
// Optimization: if we can immediately resolve this, don't bother
// registering a Token.
if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
return this.valueOrFunction.toString();
// registering a Token (unless it's already a token).
if (typeof(this.valueOrFunction) === 'string') {
return this.valueOrFunction;
}

if (this.tokenStringification === undefined) {
this.tokenStringification = TokenMap.instance().registerString(this, this.displayName);
}

return this.tokenStringification;
}

Expand Down Expand Up @@ -139,9 +139,8 @@ export class Token {
* is constructing a `FnJoin` or a `FnSelect` on it.
*/
public toList(): string[] {
const valueType = typeof this.valueOrFunction;
if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
throw this.newError('Got a literal Token value; only intrinsics can ever evaluate to lists.');
if (Array.isArray(this.valueOrFunction)) {
return this.valueOrFunction;
}

if (this.tokenListification === undefined) {
Expand All @@ -160,14 +159,13 @@ export class Token {
* other operations can and probably will destroy the token-ness of the value.
*/
public toNumber(): number {
// Optimization: if we can immediately resolve this, don't bother
// registering a Token.
if (typeof(this.valueOrFunction) === 'number') {
return this.valueOrFunction;
}

if (this.tokenNumberification === undefined) {
const valueType = typeof this.valueOrFunction;
// Optimization: if we can immediately resolve this, don't bother
// registering a Token.
if (valueType === 'number') { return this.valueOrFunction; }
if (valueType !== 'function') {
throw this.newError(`Token value is not number or lazy, can't represent as number: ${this.valueOrFunction}`);
}
this.tokenNumberification = TokenMap.instance().registerNumber(this);
}

Expand Down
89 changes: 87 additions & 2 deletions packages/@aws-cdk/cdk/test/test.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { App as Root, findTokens, Fn, Stack, Token } from '../lib';
import { createTokenDouble, extractTokenDouble } from '../lib/encoding';
import { TokenMap } from '../lib/token-map';
import { evaluateCFN } from './evaluate-cfn';
import { func } from 'fast-check/*';

export = {
'resolve a plain old object should just return the object'(test: Test) {
Expand Down Expand Up @@ -467,15 +468,14 @@ export = {

'can number-encode and resolve Token objects'(test: Test) {
// GIVEN
const stack = new Stack();
const x = new Token(() => 123);

// THEN
const encoded = x.toNumber();
test.equal(true, Token.isToken(encoded), 'encoded number does not test as token');

// THEN
const resolved = stack.node.resolve({ value: encoded });
const resolved = resolve({ value: encoded });
test.deepEqual(resolved, { value: 123 });

test.done();
Expand Down Expand Up @@ -522,6 +522,91 @@ export = {
const token = fn1();
test.throws(() => token.throwError('message!'), /Token created:/);
test.done();
},

'type coercion': (() => {
const tests: any = { };

const inputs = [
() => 'lazy',
'a string',
1234,
{ an_object: 1234 },
[ 1, 2, 3 ],
false
];

for (const input of inputs) {
// GIVEN
const stringToken = new Token(input).toString();
const numberToken = new Token(input).toNumber();
const listToken = new Token(input).toList();

// THEN
const expected = typeof(input) === 'function' ? input() : input;

tests[`${input}<string>.toNumber()`] = (test: Test) => {
test.deepEqual(resolve(new Token(stringToken).toNumber()), expected);
test.done();
};

tests[`${input}<list>.toNumber()`] = (test: Test) => {
test.deepEqual(resolve(new Token(listToken).toNumber()), expected);
test.done();
};

tests[`${input}<number>.toNumber()`] = (test: Test) => {
test.deepEqual(resolve(new Token(numberToken).toNumber()), expected);
test.done();
};

tests[`${input}<string>.toString()`] = (test: Test) => {
test.deepEqual(resolve(new Token(stringToken).toString()), expected);
test.done();
};

tests[`${input}<list>.toString()`] = (test: Test) => {
test.deepEqual(resolve(new Token(listToken).toString()), expected);
test.done();
};

tests[`${input}<number>.toString()`] = (test: Test) => {
test.deepEqual(resolve(new Token(numberToken).toString()), expected);
test.done();
};

tests[`${input}<string>.toList()`] = (test: Test) => {
test.deepEqual(resolve(new Token(stringToken).toList()), expected);
test.done();
};

tests[`${input}<list>.toList()`] = (test: Test) => {
test.deepEqual(resolve(new Token(listToken).toList()), expected);
test.done();
};

tests[`${input}<number>.toList()`] = (test: Test) => {
test.deepEqual(resolve(new Token(numberToken).toList()), expected);
test.done();
};
}

return tests;
})(),

'toXxx short circuts if the input is of the same type': {
'toNumber(number)'(test: Test) {
test.deepEqual(new Token(123).toNumber(), 123);
test.done();
},
'toList(list)'(test: Test) {
test.deepEqual(new Token([1, 2, 3]).toList(), [1, 2, 3]);
test.done();
},
'toString(string)'(test: Test) {
test.deepEqual(new Token('string').toString(), 'string'),
test.done();
}
}
};

Expand Down

0 comments on commit 5b0730f

Please sign in to comment.