From b9eeaa0a7d06da5626fe2f49509249c476187631 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 13 May 2019 18:16:25 +0200 Subject: [PATCH] feat(cdk): support encoding Tokens as numbers (#2534) Numbers can now be encoded into a set of (hugely negative) numbers, and the encoding can be reversed. This allows APIs that take numbers to take lazy values and intrinsics that evaluate to numbers. This change only introduces the capability, it does not use it in any of the construct libraries yet. Fixes #1455. --- .../@aws-cdk/app-delivery/package-lock.json | 2 +- packages/@aws-cdk/applet-js/package-lock.json | 2 +- packages/@aws-cdk/assert/package-lock.json | 43 +++++-- packages/@aws-cdk/assets/package-lock.json | 2 +- .../package-lock.json | 2 +- .../aws-autoscaling-common/package-lock.json | 2 +- .../package-lock.json | 2 +- .../@aws-cdk/aws-cloudfront/package-lock.json | 2 +- .../@aws-cdk/aws-cloudtrail/package-lock.json | 2 +- .../@aws-cdk/aws-codebuild/package-lock.json | 2 +- .../@aws-cdk/aws-codecommit/package-lock.json | 2 +- .../package-lock.json | 2 +- .../package-lock.json | 2 +- .../aws-events-targets/package-lock.json | 2 +- .../@aws-cdk/aws-lambda/package-lock.json | 2 +- .../@aws-cdk/aws-route53/package-lock.json | 2 +- packages/@aws-cdk/aws-sqs/package-lock.json | 2 +- .../aws-stepfunctions-tasks/package-lock.json | 2 +- packages/@aws-cdk/cdk/lib/encoding.ts | 108 +++++++++++++++++- packages/@aws-cdk/cdk/lib/resolve.ts | 13 +++ packages/@aws-cdk/cdk/lib/token-map.ts | 33 +++++- packages/@aws-cdk/cdk/lib/token.ts | 25 ++++ packages/@aws-cdk/cdk/package-lock.json | 2 +- packages/@aws-cdk/cdk/test/test.tokens.ts | 51 ++++++++- packages/@aws-cdk/cfnspec/package-lock.json | 2 +- .../cloudformation-diff/package-lock.json | 2 +- packages/aws-cdk/package-lock.json | 2 +- packages/cdk-dasm/package-lock.json | 2 +- .../simple-resource-bundler/package-lock.json | 2 +- tools/awslint/package-lock.json | 2 +- tools/cdk-build-tools/package-lock.json | 2 +- tools/cdk-integ-tools/package-lock.json | 2 +- tools/cfn2ts/package-lock.json | 2 +- tools/pkglint/package-lock.json | 2 +- tools/pkgtools/package-lock.json | 2 +- 35 files changed, 282 insertions(+), 49 deletions(-) diff --git a/packages/@aws-cdk/app-delivery/package-lock.json b/packages/@aws-cdk/app-delivery/package-lock.json index db19991690a51..88aad3fcb4f83 100644 --- a/packages/@aws-cdk/app-delivery/package-lock.json +++ b/packages/@aws-cdk/app-delivery/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/app-delivery", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/applet-js/package-lock.json b/packages/@aws-cdk/applet-js/package-lock.json index 35b5598dc5ccd..3d6aa703b1608 100644 --- a/packages/@aws-cdk/applet-js/package-lock.json +++ b/packages/@aws-cdk/applet-js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/applet-js", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/assert/package-lock.json b/packages/@aws-cdk/assert/package-lock.json index afbd964739512..2dffe0f6a5106 100644 --- a/packages/@aws-cdk/assert/package-lock.json +++ b/packages/@aws-cdk/assert/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1444,7 +1444,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1462,11 +1463,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1479,15 +1482,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1590,7 +1596,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1600,6 +1607,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1612,17 +1620,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1639,6 +1650,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1711,7 +1723,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1721,6 +1734,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1796,7 +1810,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1826,6 +1841,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1843,6 +1859,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1881,11 +1898,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/packages/@aws-cdk/assets/package-lock.json b/packages/@aws-cdk/assets/package-lock.json index 9497b01aeb055..c43201dfaeada 100644 --- a/packages/@aws-cdk/assets/package-lock.json +++ b/packages/@aws-cdk/assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json index 4294183a58817..c94960da9f871 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-applicationautoscaling", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json index 9aadca8a42d06..2392efb01585f 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-common", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json index d5c5785bc49ed..6824405d1fad1 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json @@ -1,6 +1,6 @@ { "name": "dns_validated_certificate_handler", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudfront/package-lock.json b/packages/@aws-cdk/aws-cloudfront/package-lock.json index e6d7537ce489f..3b9b386d18c95 100644 --- a/packages/@aws-cdk/aws-cloudfront/package-lock.json +++ b/packages/@aws-cdk/aws-cloudfront/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudfront", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudtrail/package-lock.json b/packages/@aws-cdk/aws-cloudtrail/package-lock.json index c18f48597418d..85ff8dd8f3fa8 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package-lock.json +++ b/packages/@aws-cdk/aws-cloudtrail/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudtrail", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codebuild/package-lock.json b/packages/@aws-cdk/aws-codebuild/package-lock.json index fe0d06104bc98..4a1689214859a 100644 --- a/packages/@aws-cdk/aws-codebuild/package-lock.json +++ b/packages/@aws-cdk/aws-codebuild/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codebuild", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codecommit/package-lock.json b/packages/@aws-cdk/aws-codecommit/package-lock.json index 5b158d030bc56..0210b68df6aae 100644 --- a/packages/@aws-cdk/aws-codecommit/package-lock.json +++ b/packages/@aws-cdk/aws-codecommit/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codecommit", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json index be8a1d5a85604..6edd1baeff44c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline-actions", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json index 282a449c8ebde..ca0ca5c7743fa 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-global-lambda-coordinator", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-events-targets/package-lock.json b/packages/@aws-cdk/aws-events-targets/package-lock.json index 5150e1711514c..d24eb4a6e603c 100644 --- a/packages/@aws-cdk/aws-events-targets/package-lock.json +++ b/packages/@aws-cdk/aws-events-targets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-events-targets", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda/package-lock.json b/packages/@aws-cdk/aws-lambda/package-lock.json index 8d10e101bb79b..899890f49ed6b 100644 --- a/packages/@aws-cdk/aws-lambda/package-lock.json +++ b/packages/@aws-cdk/aws-lambda/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/package-lock.json b/packages/@aws-cdk/aws-route53/package-lock.json index c2af9f7c26cdc..ccf1d80df07a4 100644 --- a/packages/@aws-cdk/aws-route53/package-lock.json +++ b/packages/@aws-cdk/aws-route53/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-sqs/package-lock.json b/packages/@aws-cdk/aws-sqs/package-lock.json index 1a2e5b07d72fe..f3ba0e70d68bd 100644 --- a/packages/@aws-cdk/aws-sqs/package-lock.json +++ b/packages/@aws-cdk/aws-sqs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sqs", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json index 895967b36848e..90a8de80e8371 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions-tasks", - "version": "0.28.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/lib/encoding.ts b/packages/@aws-cdk/cdk/lib/encoding.ts index f556adb75e230..d5d41a1cab7fd 100644 --- a/packages/@aws-cdk/cdk/lib/encoding.ts +++ b/packages/@aws-cdk/cdk/lib/encoding.ts @@ -200,9 +200,115 @@ export function containsListTokenElement(xs: any[]) { export function unresolved(obj: any): boolean { if (typeof(obj) === 'string') { return TokenString.forStringToken(obj).test(); + } else if (typeof obj === 'number') { + return extractTokenDouble(obj) !== undefined; } else if (Array.isArray(obj) && obj.length === 1) { return typeof(obj[0]) === 'string' && TokenString.forListToken(obj[0]).test(); } else { return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; } -} \ No newline at end of file +} + +/** + * Bit pattern in the top 16 bits of a double to indicate a Token + * + * An IEEE double in LE memory order looks like this (grouped + * into octets, then grouped into 32-bit words): + * + * mmmmmmmm.mmmmmmmm.mmmmmmmm.mmmmmmmm | mmmmmmmm.mmmmmmmm.EEEEmmmm.sEEEEEEE + * + * - m: mantissa (52 bits) + * - E: exponent (11 bits) + * - s: sign (1 bit) + * + * We put the following marker into the top 16 bits (exponent and sign), and + * use the mantissa part to encode the token index. To save some bit twiddling + * we use all top 16 bits for the tag. That loses us 4 mantissa bits to store + * information in but we still have 48, which is going to be plenty for any + * number of tokens to be created during the lifetime of any CDK application. + * + * Can't have all bits set because that makes a NaN, so unset the least + * significant exponent bit. + * + * Currently not supporting BE architectures. + */ +// tslint:disable-next-line:no-bitwise +const DOUBLE_TOKEN_MARKER_BITS = 0xFBFF << 16; + +/** + * Highest encodable number + */ +const MAX_ENCODABLE_INTEGER = Math.pow(2, 48) - 1; + +/** + * Get 2^32 as a number, so we can do multiplication and div instead of bit shifting + * + * Necessary because in JavaScript, bit operations implicitly convert + * to int32 and we need them to work on "int64"s. + * + * So instead of x >> 32, we do Math.floor(x / 2^32), and vice versa. + */ +const BITS32 = Math.pow(2, 32); + +/** + * Return a special Double value that encodes the given nonnegative integer + * + * We use this to encode Token ordinals. + */ +export function createTokenDouble(x: number) { + if (Math.floor(x) !== x || x < 0) { + throw new Error('Can only encode positive integers'); + } + if (x > MAX_ENCODABLE_INTEGER) { + throw new Error(`Got an index too large to encode: ${x}`); + } + + const buf = new ArrayBuffer(8); + const ints = new Uint32Array(buf); + + // tslint:disable:no-bitwise + ints[0] = x & 0x0000FFFFFFFF; // Bottom 32 bits of number + + // This needs an "x >> 32" but that will make it a 32-bit number instead + // of a 64-bit number. + ints[1] = (shr32(x) & 0xFFFF) | DOUBLE_TOKEN_MARKER_BITS; // Top 16 bits of number and the mask + // tslint:enable:no-bitwise + + return (new Float64Array(buf))[0]; +} + +/** + * Shift a 64-bit int right 32 bits + */ +function shr32(x: number) { + return Math.floor(x / BITS32); +} + +/** + * Shift a 64-bit left 32 bits + */ +function shl32(x: number) { + return x * BITS32; +} + +/** + * Extract the encoded integer out of the special Double value + * + * Returns undefined if the float is a not an encoded token. + */ +export function extractTokenDouble(encoded: number): number | undefined { + const buf = new ArrayBuffer(8); + (new Float64Array(buf))[0] = encoded; + + const ints = new Uint32Array(buf); + + // tslint:disable:no-bitwise + if ((ints[1] & 0xFFFF0000) !== DOUBLE_TOKEN_MARKER_BITS) { + return undefined; + } + + // Must use + instead of | here (bitwise operations + // will force 32-bits integer arithmetic, + will not). + return ints[0] + shl32(ints[1] & 0xFFFF); + // tslint:enable:no-bitwise +} diff --git a/packages/@aws-cdk/cdk/lib/resolve.ts b/packages/@aws-cdk/cdk/lib/resolve.ts index beb21cb17b6b4..dbc957e1b6a1a 100644 --- a/packages/@aws-cdk/cdk/lib/resolve.ts +++ b/packages/@aws-cdk/cdk/lib/resolve.ts @@ -54,6 +54,13 @@ export function resolve(obj: any, context: ResolveContext): any { return resolveStringTokens(obj, context); } + // + // number - potentially decode Tokenized number + // + if (typeof(obj) === 'number') { + return resolveNumberToken(obj, context); + } + // // primitives - as-is // @@ -184,3 +191,9 @@ function resolveListTokens(xs: string[], context: ResolveContext): any { } return fragments.mapUnresolved(x => resolve(x, context)).values[0]; } + +function resolveNumberToken(x: number, context: ResolveContext): any { + const token = TokenMap.instance().lookupNumberToken(x); + if (token === undefined) { return x; } + return resolve(token, context); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/token-map.ts b/packages/@aws-cdk/cdk/lib/token-map.ts index 3dd31ad0d8481..2db15eeb855d0 100644 --- a/packages/@aws-cdk/cdk/lib/token-map.ts +++ b/packages/@aws-cdk/cdk/lib/token-map.ts @@ -1,4 +1,5 @@ -import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, END_TOKEN_MARKER, TokenString, VALID_KEY_CHARS } from "./encoding"; +import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, createTokenDouble, + END_TOKEN_MARKER, extractTokenDouble, TokenString, VALID_KEY_CHARS } from "./encoding"; import { Token } from "./token"; const glob = global as any; @@ -23,7 +24,9 @@ export class TokenMap { return glob.__cdkTokenMap; } - private readonly tokenMap = new Map(); + private readonly stringTokenMap = new Map(); + private readonly numberTokenMap = new Map(); + private tokenCounter = 0; /** * Generate a unique string for this Token, returning a key @@ -49,6 +52,15 @@ export class TokenMap { return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; } + /** + * Create a unique number representation for this Token and return it + */ + public registerNumber(token: Token): number { + const tokenIndex = this.tokenCounter++; + this.numberTokenMap.set(tokenIndex, token); + return createTokenDouble(tokenIndex); + } + /** * Reverse a string representation into a Token object */ @@ -76,13 +88,24 @@ export class TokenMap { return undefined; } + /** + * Reverse a number encoding into a Token, or undefined if the number wasn't a Token + */ + public lookupNumberToken(x: number): Token | undefined { + const tokenIndex = extractTokenDouble(x); + if (tokenIndex === undefined) { return undefined; } + const t = this.numberTokenMap.get(tokenIndex); + if (t === undefined) { throw new Error('Encoded representation of unknown number Token found'); } + return t; + } + /** * Find a Token by key. * * This excludes the token markers. */ public lookupToken(key: string): Token { - const token = this.tokenMap.get(key); + const token = this.stringTokenMap.get(key); if (!token) { throw new Error(`Unrecognized token key: ${key}`); } @@ -90,10 +113,10 @@ export class TokenMap { } private register(token: Token, representationHint?: string): string { - const counter = this.tokenMap.size; + const counter = this.tokenCounter++; const representation = (representationHint || `TOKEN`).replace(new RegExp(`[^${VALID_KEY_CHARS}]`, 'g'), '.'); const key = `${representation}.${counter}`; - this.tokenMap.set(key, token); + this.stringTokenMap.set(key, token); return key; } } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/token.ts b/packages/@aws-cdk/cdk/lib/token.ts index d78bb418d3ab7..f7184115fe840 100644 --- a/packages/@aws-cdk/cdk/lib/token.ts +++ b/packages/@aws-cdk/cdk/lib/token.ts @@ -38,6 +38,7 @@ export class Token { private tokenStringification?: string; private tokenListification?: string[]; + private tokenNumberification?: number; /** * Creates a token that resolves to `value`. @@ -132,6 +133,30 @@ export class Token { } return this.tokenListification; } + + /** + * Return a floating point representation of this Token + * + * Call this if the Token intrinsically resolves to something that represents + * a number, and you need to pass it into an API that expects a number. + * + * You may not do any operations on the returned value; any arithmetic or + * other operations can and probably will destroy the token-ness of the value. + */ + public toNumber(): number { + 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 new Error(`Token value is not number or lazy, can't represent as number: ${this.valueOrFunction}`); + } + this.tokenNumberification = TokenMap.instance().registerNumber(this); + } + + return this.tokenNumberification; + } } /** diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 863b1c33c028e..ff4995939ee42 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 259a31974eabb..51ded366e7662 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; -import { App as Root, Fn, Token } from '../lib'; +import { App as Root, Fn, Stack, Token } from '../lib'; +import { createTokenDouble, extractTokenDouble } from '../lib/encoding'; import { TokenMap } from '../lib/token-map'; import { evaluateCFN } from './evaluate-cfn'; @@ -387,7 +388,53 @@ export = { test.done(); }, - } + }, + + 'number encoding': { + 'basic integer encoding works'(test: Test) { + test.equal(16, extractTokenDouble(createTokenDouble(16))); + test.done(); + }, + + 'arbitrary integers can be encoded, stringified, and recovered'(test: Test) { + for (let i = 0; i < 100; i++) { + // We can encode all numbers up to 2^48-1 + const x = Math.floor(Math.random() * (Math.pow(2, 48) - 1)); + + const encoded = createTokenDouble(x); + // Roundtrip through JSONification + const roundtripped = JSON.parse(JSON.stringify({ theNumber: encoded })).theNumber; + const decoded = extractTokenDouble(roundtripped); + test.equal(decoded, x, `Fail roundtrip encoding of ${x}`); + } + + test.done(); + }, + + 'arbitrary numbers are correctly detected as not being tokens'(test: Test) { + test.equal(undefined, extractTokenDouble(0)); + test.equal(undefined, extractTokenDouble(1243)); + test.equal(undefined, extractTokenDouble(4835e+532)); + + test.done(); + }, + + '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 }); + test.deepEqual(resolved, { value: 123 }); + + test.done(); + }, + }, }; class Promise2 extends Token { diff --git a/packages/@aws-cdk/cfnspec/package-lock.json b/packages/@aws-cdk/cfnspec/package-lock.json index f8abce43617df..9bb3176515ad9 100644 --- a/packages/@aws-cdk/cfnspec/package-lock.json +++ b/packages/@aws-cdk/cfnspec/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cfnspec", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cloudformation-diff/package-lock.json b/packages/@aws-cdk/cloudformation-diff/package-lock.json index 99556a01b45b3..cdeadef4807b2 100644 --- a/packages/@aws-cdk/cloudformation-diff/package-lock.json +++ b/packages/@aws-cdk/cloudformation-diff/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/aws-cdk/package-lock.json b/packages/aws-cdk/package-lock.json index ef7a747f20301..7bdf1ee8afc33 100644 --- a/packages/aws-cdk/package-lock.json +++ b/packages/aws-cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-cdk", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/cdk-dasm/package-lock.json b/packages/cdk-dasm/package-lock.json index 02acf4d96249d..bdc4f2dea277b 100644 --- a/packages/cdk-dasm/package-lock.json +++ b/packages/cdk-dasm/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-dasm", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/simple-resource-bundler/package-lock.json b/packages/simple-resource-bundler/package-lock.json index f83241653f5f4..80700db39be0a 100644 --- a/packages/simple-resource-bundler/package-lock.json +++ b/packages/simple-resource-bundler/package-lock.json @@ -1,6 +1,6 @@ { "name": "simple-resource-bundler", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/awslint/package-lock.json b/tools/awslint/package-lock.json index f9d91c8cac2da..34d4cf6757c86 100644 --- a/tools/awslint/package-lock.json +++ b/tools/awslint/package-lock.json @@ -1,6 +1,6 @@ { "name": "awslint", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 764ea6f6f243e..3fa37a6a0b57d 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -3787,7 +3787,7 @@ "dependencies": { "resolve-from": { "version": "4.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "yargs-parser": { diff --git a/tools/cdk-integ-tools/package-lock.json b/tools/cdk-integ-tools/package-lock.json index d88a3ee2f7241..107e065192325 100644 --- a/tools/cdk-integ-tools/package-lock.json +++ b/tools/cdk-integ-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-integ-tools", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cfn2ts/package-lock.json b/tools/cfn2ts/package-lock.json index 186dc3b3837a8..ef749eaa9c0ef 100644 --- a/tools/cfn2ts/package-lock.json +++ b/tools/cfn2ts/package-lock.json @@ -1,6 +1,6 @@ { "name": "cfn2ts", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkglint/package-lock.json b/tools/pkglint/package-lock.json index b4f0acbb3b4ec..70bb284f83ab7 100644 --- a/tools/pkglint/package-lock.json +++ b/tools/pkglint/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkglint", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkgtools/package-lock.json b/tools/pkgtools/package-lock.json index 7519d7e8cc8b4..722f5b3631cfc 100644 --- a/tools/pkgtools/package-lock.json +++ b/tools/pkgtools/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkgtools", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": {