diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 52386680d26ba..e4ae99f92f423 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -192,9 +192,23 @@ You can also call [intrinsic functions](https://docs.aws.amazon.com/step-functio | Method | Purpose | |--------|---------| | `JsonPath.array(JsonPath.stringAt('$.Field'), ...)` | make an array from other elements. | -| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. | +| `JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4)` | partition an array. | +| `JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5)` | determine if a specific value is present in an array. | +| `JsonPath.arrayRange(1, 9, 2)` | create a new array containing a specific range of elements. | +| `JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5)` | get a specified index's value in an array. | +| `JsonPath.arrayLength(JsonPath.listAt('$.inputArray'))` | get the length of an array. | +| `JsonPath.arrayUnique(JsonPath.listAt('$.inputArray'))` | remove duplicate values from an array. | +| `JsonPath.base64Encode(JsonPath.stringAt('$.input'))` | encode data based on MIME Base64 encoding scheme. | +| `JsonPath.base64Decode(JsonPath.stringAt('$.base64'))` | decode data based on MIME Base64 decoding scheme. | +| `JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm'))` | calculate the hash value of a given input. | +| `JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2'))` | merge two JSON objects into a single object. | | `JsonPath.stringToJson(JsonPath.stringAt('$.ObjStr'))` | parse a JSON string to an object | | `JsonPath.jsonToString(JsonPath.objectAt('$.Obj'))` | stringify an object to a JSON string | +| `JsonPath.mathRandom(1, 999)` | return a random number. | +| `JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step'))` | return the sum of two numbers. | +| `JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter'))` | split a string into an array of values. | +| `JsonPath.uuid()` | return a version 4 universally unique identifier (v4 UUID). | +| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. | ## Amazon States Language diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts index 485b92b62337c..0d152d15c9656 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/fields.ts @@ -103,6 +103,161 @@ export class JsonPath { return new JsonPathToken(`States.Array(${values.map(renderInExpression).join(', ')})`).toString(); } + /** + * Make an intrinsic States.ArrayPartition expression + * + * Use this function to partition a large array. You can also use this intrinsic to slice the data and then send the payload in smaller chunks. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayPartition(array: any, chunkSize: number): string { + return new JsonPathToken(`States.ArrayPartition(${[array, chunkSize].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayContains expression + * + * Use this function to determine if a specific value is present in an array. For example, you can use this function to detect if there was an error in a Map state iteration. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayContains(array: any, value: any): string { + return new JsonPathToken(`States.ArrayContains(${[array, value].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayRange expression + * + * Use this function to create a new array containing a specific range of elements. The new array can contain up to 1000 elements. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayRange(start: number, end: number, step: number): string { + return new JsonPathToken(`States.ArrayRange(${[start, end, step].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayGetItem expression + * + * Use this function to get a specified index's value in an array. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayGetItem(array: any, index: number): string { + return new JsonPathToken(`States.ArrayGetItem(${[array, index].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.ArrayLength expression + * + * Use this function to get the length of an array. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayLength(array: any): string { + return new JsonPathToken(`States.ArrayLength(${renderInExpression(array)})`).toString(); + } + + /** + * Make an intrinsic States.ArrayUnique expression + * + * Use this function to get the length of an array. + * Use this function to remove duplicate values from an array and returns an array containing only unique elements. This function takes an array, which can be unsorted, as its sole argument. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static arrayUnique(array: any): string { + return new JsonPathToken(`States.ArrayUnique(${renderInExpression(array)})`).toString(); + } + + /** + * Make an intrinsic States.Base64Encode expression + * + * Use this function to encode data based on MIME Base64 encoding scheme. You can use this function to pass data to other AWS services without using an AWS Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static base64Encode(input: string): string { + return new JsonPathToken(`States.Base64Encode(${renderInExpression(input)})`).toString(); + } + + /** + * Make an intrinsic States.Base64Decode expression + * + * Use this function to decode data based on MIME Base64 decoding scheme. You can use this function to pass data to other AWS services without using a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static base64Decode(base64: string): string { + return new JsonPathToken(`States.Base64Decode(${renderInExpression(base64)})`).toString(); + } + + /** + * Make an intrinsic States.Hash expression + * + * Use this function to calculate the hash value of a given input. You can use this function to pass data to other AWS services without using a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static hash(data: any, algorithm: string): string { + return new JsonPathToken(`States.Hash(${[data, algorithm].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.JsonMerge expression + * + * Use this function to merge two JSON objects into a single object. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static jsonMerge(value1: any, value2: any): string { + return new JsonPathToken(`States.JsonMerge(${[value1, value2].map(renderInExpression).join(', ')}, false)`).toString(); + } + + /** + * Make an intrinsic States.MathRandom expression + * + * Use this function to return a random number between the specified start and end number. For example, you can use this function to distribute a specific task between two or more resources. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static mathRandom(start: number, end: number): string { + return new JsonPathToken(`States.MathRandom(${[start, end].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.MathAdd expression + * + * Use this function to return the sum of two numbers. For example, you can use this function to increment values inside a loop without invoking a Lambda function. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static mathAdd(num1: number, num2: number): string { + return new JsonPathToken(`States.MathAdd(${[num1, num2].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.StringSplit expression + * + * Use this function to split a string into an array of values. This function takes two arguments.The first argument is a string and the second argument is the delimiting character that the function will use to divide the string. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static stringSplit(inputString: string, splitter: string): string { + return new JsonPathToken(`States.StringSplit(${[inputString, splitter].map(renderInExpression).join(', ')})`).toString(); + } + + /** + * Make an intrinsic States.UUID expression + * + * Use this function to return a version 4 universally unique identifier (v4 UUID) generated using random numbers. For example, you can use this function to call other AWS services or resources that need a UUID parameter or insert items in a DynamoDB table. + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html + */ + public static uuid(): string { + return new JsonPathToken('States.UUID()').toString(); + } + /** * Make an intrinsic States.Format expression * @@ -298,14 +453,45 @@ export class FieldUtils { } function validateJsonPath(path: string) { + const intrinsicFunctionNames = [ + // Intrinsics for arrays + 'Array', + 'ArrayPartition', + 'ArrayContains', + 'ArrayRange', + 'ArrayGetItem', + 'ArrayLength', + 'ArrayUnique', + // Intrinsics for data encoding and decoding + 'Base64Encode', + 'Base64Decode', + // Intrinsic for hash calculation + 'Hash', + // Intrinsics for JSON data manipulation + 'JsonMerge', + 'StringToJson', + 'JsonToString', + // Intrinsics for Math operations + 'MathRandom', + 'MathAdd', + // Intrinsic for String operation + 'StringSplit', + // Intrinsic for unique identifier generation + 'UUID', + // Intrinsic for generic operation + 'Format', + ]; + const intrinsicFunctionFullNames = intrinsicFunctionNames.map((fn) => `States.${fn}`); if (path !== '$' && !path.startsWith('$.') && path !== '$$' && !path.startsWith('$$.') && !path.startsWith('$[') - && ['Format', 'StringToJson', 'JsonToString', 'Array'].every(fn => !path.startsWith(`States.${fn}`)) + && intrinsicFunctionFullNames.every(fn => !path.startsWith(fn)) ) { - throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.', start with '$[', or start with an intrinsic function: States.Format, States.StringToJson, States.JsonToString, or States.Array. Received: ${path}`); + const lastItem = intrinsicFunctionFullNames.pop(); + const intrinsicFunctionsStr = intrinsicFunctionFullNames.join(', ') + ', or ' + lastItem; + throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.', start with '$[', or start with an intrinsic function: ${intrinsicFunctionsStr}. Received: ${path}`); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts b/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts index 7263da227261f..d6802227c4305 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts @@ -312,17 +312,22 @@ function pathFromToken(token: IResolvable | undefined) { } /** - * Render the string in a valid JSON Path expression. + * Render the string or number value in a valid JSON Path expression. * - * If the string is a Tokenized JSON path reference -- return the JSON path reference inside it. - * Otherwise, single-quote it. + * If the value is a Tokenized JSON path reference -- return the JSON path reference inside it. + * If the value is a number -- convert it to string. + * If the value is a string -- single-quote it. + * Otherwise, throw errors. * * Call this function whenever you're building compound JSONPath expressions, in * order to avoid having tokens-in-tokens-in-tokens which become very hard to parse. */ -export function renderInExpression(x: string) { - const path = jsonPathString(x); - return path ?? singleQuotestring(x); +export function renderInExpression(x: any) { + const path = jsonPathFromAny(x); + if (path) return path; + if (typeof x === 'number') return x.toString(10); + if (typeof x === 'string') return singleQuotestring(x); + throw new Error('Unxexpected value.'); } function singleQuotestring(x: string) { @@ -341,4 +346,4 @@ function singleQuotestring(x: string) { } ret.push("'"); return ret.join(''); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 3acbb95036924..fa3ee095f4187 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -1,7 +1,7 @@ import { FieldUtils, JsonPath, TaskInput } from '../lib'; describe('Fields', () => { - const jsonPathValidationErrorMsg = /exactly '\$', '\$\$', start with '\$.', start with '\$\$.', start with '\$\[', or start with an intrinsic function: States.Format, States.StringToJson, States.JsonToString, or States.Array./; + const jsonPathValidationErrorMsg = /exactly '\$', '\$\$', start with '\$.', start with '\$\$.', start with '\$\[', or start with an intrinsic function: States.Array, States.ArrayPartition, States.ArrayContains, States.ArrayRange, States.ArrayGetItem, States.ArrayLength, States.ArrayUnique, States.Base64Encode, States.Base64Decode, States.Hash, States.JsonMerge, States.StringToJson, States.JsonToString, States.MathRandom, States.MathAdd, States.StringSplit, States.UUID, or States.Format./; test('deep replace correctly handles fields in arrays', () => { expect( @@ -81,6 +81,20 @@ describe('Fields', () => { expect(JsonPath.stringAt('States.StringToJson')).toBeDefined(); expect(JsonPath.stringAt('States.JsonToString')).toBeDefined(); expect(JsonPath.stringAt('States.Array')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayPartition')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayContains')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayRange')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayGetItem')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayLength')).toBeDefined(); + expect(JsonPath.stringAt('States.ArrayUnique')).toBeDefined(); + expect(JsonPath.stringAt('States.Base64Encode')).toBeDefined(); + expect(JsonPath.stringAt('States.Base64Decode')).toBeDefined(); + expect(JsonPath.stringAt('States.Hash')).toBeDefined(); + expect(JsonPath.stringAt('States.JsonMerge')).toBeDefined(); + expect(JsonPath.stringAt('States.MathRandom')).toBeDefined(); + expect(JsonPath.stringAt('States.MathAdd')).toBeDefined(); + expect(JsonPath.stringAt('States.StringSplit')).toBeDefined(); + expect(JsonPath.stringAt('States.UUID')).toBeDefined(); expect(() => JsonPath.stringAt('$hello')).toThrowError(jsonPathValidationErrorMsg); expect(() => JsonPath.stringAt('hello')).toThrowError(jsonPathValidationErrorMsg); @@ -229,6 +243,184 @@ describe('intrinsics constructors', () => { }); }); + test('arrayPartition', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4), + })).toEqual({ + 'Field.$': 'States.ArrayPartition($.inputArray, 4)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.chunkSize')), + })).toEqual({ + 'Field.$': 'States.ArrayPartition($.inputArray, $.chunkSize)', + }); + }); + + test('arrayContains', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5), + })).toEqual({ + 'Field.$': 'States.ArrayContains($.inputArray, 5)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 'a'), + })).toEqual({ + 'Field.$': "States.ArrayContains($.inputArray, 'a')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.lookingFor')), + })).toEqual({ + 'Field.$': 'States.ArrayContains($.inputArray, $.lookingFor)', + }); + }); + + test('arrayRange', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayRange(1, 9, 2), + })).toEqual({ + 'Field.$': 'States.ArrayRange(1, 9, 2)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayRange(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end'), JsonPath.numberAt('$.step')), + })).toEqual({ + 'Field.$': 'States.ArrayRange($.start, $.end, $.step)', + }); + }); + + test('arrayGetItem', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5), + })).toEqual({ + 'Field.$': 'States.ArrayGetItem($.inputArray, 5)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayGetItem(JsonPath.numberAt('$.inputArray'), JsonPath.numberAt('$.index')), + })).toEqual({ + 'Field.$': 'States.ArrayGetItem($.inputArray, $.index)', + }); + }); + + test('arrayLength', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayLength(JsonPath.listAt('$.inputArray')), + })).toEqual({ + 'Field.$': 'States.ArrayLength($.inputArray)', + }); + }); + + test('arrayUnique', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.arrayUnique(JsonPath.listAt('$.inputArray')), + })).toEqual({ + 'Field.$': 'States.ArrayUnique($.inputArray)', + }); + }); + + test('base64Encode', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Encode('Data to encode'), + })).toEqual({ + 'Field.$': "States.Base64Encode('Data to encode')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Encode(JsonPath.stringAt('$.input')), + })).toEqual({ + 'Field.$': 'States.Base64Encode($.input)', + }); + }); + + test('base64Decode', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Decode('RGF0YSB0byBlbmNvZGU='), + })).toEqual({ + 'Field.$': "States.Base64Decode('RGF0YSB0byBlbmNvZGU=')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.base64Decode(JsonPath.stringAt('$.base64')), + })).toEqual({ + 'Field.$': 'States.Base64Decode($.base64)', + }); + }); + + test('hash', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.hash('Input data', 'SHA-1'), + })).toEqual({ + 'Field.$': "States.Hash('Input data', 'SHA-1')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm')), + })).toEqual({ + 'Field.$': 'States.Hash($.Data, $.Algorithm)', + }); + }); + + test('jsonMerge', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2')), + })).toEqual({ + 'Field.$': 'States.JsonMerge($.Obj1, $.Obj2, false)', + }); + }); + + test('mathRandom', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.mathRandom(1, 999), + })).toEqual({ + 'Field.$': 'States.MathRandom(1, 999)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.mathRandom(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end')), + })).toEqual({ + 'Field.$': 'States.MathRandom($.start, $.end)', + }); + }); + + test('mathAdd', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.mathAdd(1, 999), + })).toEqual({ + 'Field.$': 'States.MathAdd(1, 999)', + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step')), + })).toEqual({ + 'Field.$': 'States.MathAdd($.value1, $.step)', + }); + }); + + test('stringSplit', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.stringSplit('1,2,3,4,5', ','), + })).toEqual({ + 'Field.$': "States.StringSplit('1,2,3,4,5', ',')", + }); + + expect(FieldUtils.renderObject({ + Field: JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter')), + })).toEqual({ + 'Field.$': 'States.StringSplit($.inputString, $.splitter)', + }); + }); + + test('uuid', () => { + expect(FieldUtils.renderObject({ + Field: JsonPath.uuid(), + })).toEqual({ + 'Field.$': 'States.UUID()', + }); + }); + test('format', () => { expect(FieldUtils.renderObject({ Field: JsonPath.format('Hi my name is {}.', JsonPath.stringAt('$.Name')), @@ -267,4 +459,4 @@ test('find task token even if nested in intrinsic functions', () => { // Even if it's a hand-written literal and doesn't use our constructors expect(FieldUtils.containsTaskToken({ x: JsonPath.stringAt('States.Array($$.Task.Token)') })).toEqual(true); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts b/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts new file mode 100644 index 0000000000000..f07836da4c30e --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/integ.intrinsics.ts @@ -0,0 +1,56 @@ +import * as cdk from '@aws-cdk/core'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests'; +import { JsonPath, Pass, StateMachine } from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-stepfunctions-intrinsics-integ'); + +const pass = new Pass(stack, 'pass', { + parameters: { + array1: JsonPath.array('asdf', JsonPath.stringAt('$.Id')), + arrayPartition1: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4), + arrayPartition2: JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.chunkSize')), + arrayContains1: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5), + arrayContains2: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 'a'), + arrayContains3: JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), JsonPath.numberAt('$.lookingFor')), + arrayRange1: JsonPath.arrayRange(1, 9, 2), + arrayRange2: JsonPath.arrayRange(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end'), JsonPath.numberAt('$.step')), + arrayGetItem1: JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5), + arrayGetItem2: JsonPath.arrayGetItem(JsonPath.numberAt('$.inputArray'), JsonPath.numberAt('$.index')), + arrayLength1: JsonPath.arrayLength(JsonPath.listAt('$.inputArray')), + arrayUnique1: JsonPath.arrayUnique(JsonPath.listAt('$.inputArray')), + base64Encode1: JsonPath.base64Encode('Data to encode'), + base64Encode2: JsonPath.base64Encode(JsonPath.stringAt('$.input')), + base64Decode1: JsonPath.base64Decode('RGF0YSB0byBlbmNvZGU='), + base64Decode2: JsonPath.base64Decode(JsonPath.stringAt('$.base64')), + hash1: JsonPath.hash('Input data', 'SHA-1'), + hash2: JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm')), + jsonMerge1: JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2')), + mathRandom1: JsonPath.mathRandom(1, 999), + mathRandom2: JsonPath.mathRandom(JsonPath.numberAt('$.start'), JsonPath.numberAt('$.end')), + mathAdd1: JsonPath.mathAdd(1, 999), + mathAdd2: JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step')), + stringSplit1: JsonPath.stringSplit('1,2,3,4,5', ','), + stringSplit2: JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter')), + uuid: JsonPath.uuid(), + format1: JsonPath.format('Hi my name is {}.', JsonPath.stringAt('$.Name')), + format2: JsonPath.format(JsonPath.stringAt('$.Format'), JsonPath.stringAt('$.Name')), + stringToJson1: JsonPath.stringToJson(JsonPath.stringAt('$.Str')), + jsonToString1: JsonPath.jsonToString(JsonPath.objectAt('$.Obj')), + }, +}); + +const stateMachine = new StateMachine(stack, 'StateMachine', { + definition: pass, +}); + +const integ = new IntegTest(app, 'StateMachineIntrinsicsTest', { + testCases: [stack], +}); +integ.assertions.awsApiCall('StepFunctions', 'describeStateMachine', { + stateMachineArn: stateMachine.stateMachineArn, +}).expect(ExpectedResult.objectLike({ + status: 'ACTIVE', +})); + +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json new file mode 100644 index 0000000000000..b732375a777a4 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json @@ -0,0 +1,32 @@ +{ + "version": "21.0.0", + "files": { + "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac": { + "source": { + "path": "asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0": { + "source": { + "path": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json new file mode 100644 index 0000000000000..52f6684a436e2 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "AwsApiCallStepFunctionsdescribeStateMachine": { + "Type": "Custom::DeployAssert@SdkCallStepFunctionsdescribeStateMachin", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "StepFunctions", + "api": "describeStateMachine", + "expected": "{\"$ObjectLike\":{\"status\":\"ACTIVE\"}}", + "parameters": { + "stateMachineArn": { + "Fn::ImportValue": "aws-stepfunctions-intrinsics-integ:ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + }, + "flattenResponse": "false", + "salt": "1666195539323" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "states:DescribeStateMachine" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallStepFunctionsdescribeStateMachine": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallStepFunctionsdescribeStateMachine", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js new file mode 100644 index 0000000000000..6bee1ced2a9b7 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/asset.3074b79e05e7b98930b6449e01baa3e68b32ecff86328933c2542f7b7fe6fdac.bundle/index.js @@ -0,0 +1,767 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + const resp = request2.flattenResponse === "true" ? flatData : respond; + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json new file mode 100644 index 0000000000000..d2e1e83193b8a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.assets.json @@ -0,0 +1,19 @@ +{ + "version": "21.0.0", + "files": { + "3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c": { + "source": { + "path": "aws-stepfunctions-intrinsics-integ.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json new file mode 100644 index 0000000000000..4b26dc59ad824 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/aws-stepfunctions-intrinsics-integ.template.json @@ -0,0 +1,182 @@ +{ + "Resources": { + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "states" + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "DefinitionString": "{\"StartAt\":\"pass\",\"States\":{\"pass\":{\"Type\":\"Pass\",\"Parameters\":{\"array1.$\":\"States.Array('asdf', $.Id)\",\"arrayPartition1.$\":\"States.ArrayPartition($.inputArray, 4)\",\"arrayPartition2.$\":\"States.ArrayPartition($.inputArray, $.chunkSize)\",\"arrayContains1.$\":\"States.ArrayContains($.inputArray, 5)\",\"arrayContains2.$\":\"States.ArrayContains($.inputArray, 'a')\",\"arrayContains3.$\":\"States.ArrayContains($.inputArray, $.lookingFor)\",\"arrayRange1.$\":\"States.ArrayRange(1, 9, 2)\",\"arrayRange2.$\":\"States.ArrayRange($.start, $.end, $.step)\",\"arrayGetItem1.$\":\"States.ArrayGetItem($.inputArray, 5)\",\"arrayGetItem2.$\":\"States.ArrayGetItem($.inputArray, $.index)\",\"arrayLength1.$\":\"States.ArrayLength($.inputArray)\",\"arrayUnique1.$\":\"States.ArrayUnique($.inputArray)\",\"base64Encode1.$\":\"States.Base64Encode('Data to encode')\",\"base64Encode2.$\":\"States.Base64Encode($.input)\",\"base64Decode1.$\":\"States.Base64Decode('RGF0YSB0byBlbmNvZGU=')\",\"base64Decode2.$\":\"States.Base64Decode($.base64)\",\"hash1.$\":\"States.Hash('Input data', 'SHA-1')\",\"hash2.$\":\"States.Hash($.Data, $.Algorithm)\",\"jsonMerge1.$\":\"States.JsonMerge($.Obj1, $.Obj2, false)\",\"mathRandom1.$\":\"States.MathRandom(1, 999)\",\"mathRandom2.$\":\"States.MathRandom($.start, $.end)\",\"mathAdd1.$\":\"States.MathAdd(1, 999)\",\"mathAdd2.$\":\"States.MathAdd($.value1, $.step)\",\"stringSplit1.$\":\"States.StringSplit('1,2,3,4,5', ',')\",\"stringSplit2.$\":\"States.StringSplit($.inputString, $.splitter)\",\"uuid.$\":\"States.UUID()\",\"format1.$\":\"States.Format('Hi my name is {}.', $.Name)\",\"format2.$\":\"States.Format($.Format, $.Name)\",\"stringToJson1.$\":\"States.StringToJson($.Str)\",\"jsonToString1.$\":\"States.JsonToString($.Obj)\"},\"End\":true}}}" + }, + "DependsOn": [ + "StateMachineRoleB840431D" + ] + } + }, + "Mappings": { + "ServiceprincipalMap": { + "af-south-1": { + "states": "states.af-south-1.amazonaws.com" + }, + "ap-east-1": { + "states": "states.ap-east-1.amazonaws.com" + }, + "ap-northeast-1": { + "states": "states.ap-northeast-1.amazonaws.com" + }, + "ap-northeast-2": { + "states": "states.ap-northeast-2.amazonaws.com" + }, + "ap-northeast-3": { + "states": "states.ap-northeast-3.amazonaws.com" + }, + "ap-south-1": { + "states": "states.ap-south-1.amazonaws.com" + }, + "ap-southeast-1": { + "states": "states.ap-southeast-1.amazonaws.com" + }, + "ap-southeast-2": { + "states": "states.ap-southeast-2.amazonaws.com" + }, + "ap-southeast-3": { + "states": "states.ap-southeast-3.amazonaws.com" + }, + "ca-central-1": { + "states": "states.ca-central-1.amazonaws.com" + }, + "cn-north-1": { + "states": "states.cn-north-1.amazonaws.com" + }, + "cn-northwest-1": { + "states": "states.cn-northwest-1.amazonaws.com" + }, + "eu-central-1": { + "states": "states.eu-central-1.amazonaws.com" + }, + "eu-north-1": { + "states": "states.eu-north-1.amazonaws.com" + }, + "eu-south-1": { + "states": "states.eu-south-1.amazonaws.com" + }, + "eu-south-2": { + "states": "states.eu-south-2.amazonaws.com" + }, + "eu-west-1": { + "states": "states.eu-west-1.amazonaws.com" + }, + "eu-west-2": { + "states": "states.eu-west-2.amazonaws.com" + }, + "eu-west-3": { + "states": "states.eu-west-3.amazonaws.com" + }, + "me-south-1": { + "states": "states.me-south-1.amazonaws.com" + }, + "sa-east-1": { + "states": "states.sa-east-1.amazonaws.com" + }, + "us-east-1": { + "states": "states.us-east-1.amazonaws.com" + }, + "us-east-2": { + "states": "states.us-east-2.amazonaws.com" + }, + "us-gov-east-1": { + "states": "states.us-gov-east-1.amazonaws.com" + }, + "us-gov-west-1": { + "states": "states.us-gov-west-1.amazonaws.com" + }, + "us-iso-east-1": { + "states": "states.amazonaws.com" + }, + "us-iso-west-1": { + "states": "states.amazonaws.com" + }, + "us-isob-east-1": { + "states": "states.amazonaws.com" + }, + "us-west-1": { + "states": "states.us-west-1.amazonaws.com" + }, + "us-west-2": { + "states": "states.us-west-2.amazonaws.com" + } + } + }, + "Outputs": { + "ExportsOutputRefStateMachine2E01A3A5BA46F753": { + "Value": { + "Ref": "StateMachine2E01A3A5" + }, + "Export": { + "Name": "aws-stepfunctions-intrinsics-integ:ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json new file mode 100644 index 0000000000000..96a6796634bf0 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "21.0.0", + "testCases": { + "StateMachineIntrinsicsTest/DefaultTest": { + "stacks": [ + "aws-stepfunctions-intrinsics-integ" + ], + "assertionStack": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert", + "assertionStackName": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..610687b57db93 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/manifest.json @@ -0,0 +1,154 @@ +{ + "version": "21.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-stepfunctions-intrinsics-integ.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-stepfunctions-intrinsics-integ.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-stepfunctions-intrinsics-integ": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-stepfunctions-intrinsics-integ.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3e297f2466514c81633efd55d6b611657afa510cc934191cdd1ee23cdad3c30c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-stepfunctions-intrinsics-integ.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-stepfunctions-intrinsics-integ.assets" + ], + "metadata": { + "/aws-stepfunctions-intrinsics-integ/StateMachine/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachineRoleB840431D" + } + ], + "/aws-stepfunctions-intrinsics-integ/StateMachine/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "StateMachine2E01A3A5" + } + ], + "/aws-stepfunctions-intrinsics-integ/Service-principalMap": [ + { + "type": "aws:cdk:logicalId", + "data": "ServiceprincipalMap" + } + ], + "/aws-stepfunctions-intrinsics-integ/Exports/Output{\"Ref\":\"StateMachine2E01A3A5\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefStateMachine2E01A3A5BA46F753" + } + ], + "/aws-stepfunctions-intrinsics-integ/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-stepfunctions-intrinsics-integ/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-stepfunctions-intrinsics-integ" + }, + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9efe81f855fa5f7925cf5a69c0ef1ff9563e05b71711a017100aa3d0e6921ab0.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-stepfunctions-intrinsics-integ", + "StateMachineIntrinsicsTestDefaultTestDeployAssert1C1E1D7E.assets" + ], + "metadata": { + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallStepFunctionsdescribeStateMachine" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallStepFunctionsdescribeStateMachine" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/StateMachineIntrinsicsTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json new file mode 100644 index 0000000000000..34c717c5aa1c2 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/intrinsics.integ.snapshot/tree.json @@ -0,0 +1,264 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "aws-stepfunctions-intrinsics-integ": { + "id": "aws-stepfunctions-intrinsics-integ", + "path": "aws-stepfunctions-intrinsics-integ", + "children": { + "pass": { + "id": "pass", + "path": "aws-stepfunctions-intrinsics-integ/pass", + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.Pass", + "version": "0.0.0" + } + }, + "StateMachine": { + "id": "StateMachine", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine", + "children": { + "Role": { + "id": "Role", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::FindInMap": [ + "ServiceprincipalMap", + { + "Ref": "AWS::Region" + }, + "states" + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-stepfunctions-intrinsics-integ/StateMachine/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::StepFunctions::StateMachine", + "aws:cdk:cloudformation:props": { + "roleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + }, + "definitionString": "{\"StartAt\":\"pass\",\"States\":{\"pass\":{\"Type\":\"Pass\",\"Parameters\":{\"array1.$\":\"States.Array('asdf', $.Id)\",\"arrayPartition1.$\":\"States.ArrayPartition($.inputArray, 4)\",\"arrayPartition2.$\":\"States.ArrayPartition($.inputArray, $.chunkSize)\",\"arrayContains1.$\":\"States.ArrayContains($.inputArray, 5)\",\"arrayContains2.$\":\"States.ArrayContains($.inputArray, 'a')\",\"arrayContains3.$\":\"States.ArrayContains($.inputArray, $.lookingFor)\",\"arrayRange1.$\":\"States.ArrayRange(1, 9, 2)\",\"arrayRange2.$\":\"States.ArrayRange($.start, $.end, $.step)\",\"arrayGetItem1.$\":\"States.ArrayGetItem($.inputArray, 5)\",\"arrayGetItem2.$\":\"States.ArrayGetItem($.inputArray, $.index)\",\"arrayLength1.$\":\"States.ArrayLength($.inputArray)\",\"arrayUnique1.$\":\"States.ArrayUnique($.inputArray)\",\"base64Encode1.$\":\"States.Base64Encode('Data to encode')\",\"base64Encode2.$\":\"States.Base64Encode($.input)\",\"base64Decode1.$\":\"States.Base64Decode('RGF0YSB0byBlbmNvZGU=')\",\"base64Decode2.$\":\"States.Base64Decode($.base64)\",\"hash1.$\":\"States.Hash('Input data', 'SHA-1')\",\"hash2.$\":\"States.Hash($.Data, $.Algorithm)\",\"jsonMerge1.$\":\"States.JsonMerge($.Obj1, $.Obj2, false)\",\"mathRandom1.$\":\"States.MathRandom(1, 999)\",\"mathRandom2.$\":\"States.MathRandom($.start, $.end)\",\"mathAdd1.$\":\"States.MathAdd(1, 999)\",\"mathAdd2.$\":\"States.MathAdd($.value1, $.step)\",\"stringSplit1.$\":\"States.StringSplit('1,2,3,4,5', ',')\",\"stringSplit2.$\":\"States.StringSplit($.inputString, $.splitter)\",\"uuid.$\":\"States.UUID()\",\"format1.$\":\"States.Format('Hi my name is {}.', $.Name)\",\"format2.$\":\"States.Format($.Format, $.Name)\",\"stringToJson1.$\":\"States.StringToJson($.Str)\",\"jsonToString1.$\":\"States.JsonToString($.Obj)\"},\"End\":true}}}" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.CfnStateMachine", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-stepfunctions.StateMachine", + "version": "0.0.0" + } + }, + "Service-principalMap": { + "id": "Service-principalMap", + "path": "aws-stepfunctions-intrinsics-integ/Service-principalMap", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnMapping", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "aws-stepfunctions-intrinsics-integ/Exports", + "children": { + "Output{\"Ref\":\"StateMachine2E01A3A5\"}": { + "id": "Output{\"Ref\":\"StateMachine2E01A3A5\"}", + "path": "aws-stepfunctions-intrinsics-integ/Exports/Output{\"Ref\":\"StateMachine2E01A3A5\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "StateMachineIntrinsicsTest": { + "id": "StateMachineIntrinsicsTest", + "path": "StateMachineIntrinsicsTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "StateMachineIntrinsicsTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert", + "children": { + "AwsApiCallStepFunctionsdescribeStateMachine": { + "id": "AwsApiCallStepFunctionsdescribeStateMachine", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default", + "children": { + "Default": { + "id": "Default", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/AwsApiCallStepFunctionsdescribeStateMachine/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "StateMachineIntrinsicsTest/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.129" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts new file mode 100644 index 0000000000000..20a7b3a2bcec6 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/private/json-path.test.ts @@ -0,0 +1,26 @@ +import { JsonPath } from '../../lib'; +import { renderInExpression } from '../../lib/private/json-path'; + +describe('RenderInExpression', () => { + test('simple number', () => { + expect(renderInExpression(1)).toBe('1'); + }); + test('simple string', () => { + expect(renderInExpression('a')).toBe("'a'"); + }); + test('jsonpath stringAt', () => { + expect(renderInExpression(JsonPath.stringAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath numberAt', () => { + expect(renderInExpression(JsonPath.numberAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath listAt', () => { + expect(renderInExpression(JsonPath.listAt('$.Field'))).toBe('$.Field'); + }); + test('jsonpath objectAt', () => { + expect(renderInExpression(JsonPath.objectAt('$.Field'))).toBe('$.Field'); + }); + test('raw array', () => { + expect(() => renderInExpression([1, 2])).toThrow(); + }); +});