Skip to content

Commit 8f85b08

Browse files
authored
feat(stepfunctions): add intrinsic functions (#22431)
Resolves #22068 and resolves #22629 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent ebba9e3 commit 8f85b08

15 files changed

+2054
-12
lines changed

packages/@aws-cdk/aws-stepfunctions/README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,23 @@ You can also call [intrinsic functions](https://docs.aws.amazon.com/step-functio
192192
| Method | Purpose |
193193
|--------|---------|
194194
| `JsonPath.array(JsonPath.stringAt('$.Field'), ...)` | make an array from other elements. |
195-
| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. |
195+
| `JsonPath.arrayPartition(JsonPath.listAt('$.inputArray'), 4)` | partition an array. |
196+
| `JsonPath.arrayContains(JsonPath.listAt('$.inputArray'), 5)` | determine if a specific value is present in an array. |
197+
| `JsonPath.arrayRange(1, 9, 2)` | create a new array containing a specific range of elements. |
198+
| `JsonPath.arrayGetItem(JsonPath.listAt('$.inputArray'), 5)` | get a specified index's value in an array. |
199+
| `JsonPath.arrayLength(JsonPath.listAt('$.inputArray'))` | get the length of an array. |
200+
| `JsonPath.arrayUnique(JsonPath.listAt('$.inputArray'))` | remove duplicate values from an array. |
201+
| `JsonPath.base64Encode(JsonPath.stringAt('$.input'))` | encode data based on MIME Base64 encoding scheme. |
202+
| `JsonPath.base64Decode(JsonPath.stringAt('$.base64'))` | decode data based on MIME Base64 decoding scheme. |
203+
| `JsonPath.hash(JsonPath.objectAt('$.Data'), JsonPath.stringAt('$.Algorithm'))` | calculate the hash value of a given input. |
204+
| `JsonPath.jsonMerge(JsonPath.objectAt('$.Obj1'), JsonPath.objectAt('$.Obj2'))` | merge two JSON objects into a single object. |
196205
| `JsonPath.stringToJson(JsonPath.stringAt('$.ObjStr'))` | parse a JSON string to an object |
197206
| `JsonPath.jsonToString(JsonPath.objectAt('$.Obj'))` | stringify an object to a JSON string |
207+
| `JsonPath.mathRandom(1, 999)` | return a random number. |
208+
| `JsonPath.mathAdd(JsonPath.numberAt('$.value1'), JsonPath.numberAt('$.step'))` | return the sum of two numbers. |
209+
| `JsonPath.stringSplit(JsonPath.stringAt('$.inputString'), JsonPath.stringAt('$.splitter'))` | split a string into an array of values. |
210+
| `JsonPath.uuid()` | return a version 4 universally unique identifier (v4 UUID). |
211+
| `JsonPath.format('The value is {}.', JsonPath.stringAt('$.Value'))` | insert elements into a format string. |
198212

199213
## Amazon States Language
200214

packages/@aws-cdk/aws-stepfunctions/lib/fields.ts

+188-2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,161 @@ export class JsonPath {
103103
return new JsonPathToken(`States.Array(${values.map(renderInExpression).join(', ')})`).toString();
104104
}
105105

106+
/**
107+
* Make an intrinsic States.ArrayPartition expression
108+
*
109+
* 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.
110+
*
111+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
112+
*/
113+
public static arrayPartition(array: any, chunkSize: number): string {
114+
return new JsonPathToken(`States.ArrayPartition(${[array, chunkSize].map(renderInExpression).join(', ')})`).toString();
115+
}
116+
117+
/**
118+
* Make an intrinsic States.ArrayContains expression
119+
*
120+
* 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.
121+
*
122+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
123+
*/
124+
public static arrayContains(array: any, value: any): string {
125+
return new JsonPathToken(`States.ArrayContains(${[array, value].map(renderInExpression).join(', ')})`).toString();
126+
}
127+
128+
/**
129+
* Make an intrinsic States.ArrayRange expression
130+
*
131+
* Use this function to create a new array containing a specific range of elements. The new array can contain up to 1000 elements.
132+
*
133+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
134+
*/
135+
public static arrayRange(start: number, end: number, step: number): string {
136+
return new JsonPathToken(`States.ArrayRange(${[start, end, step].map(renderInExpression).join(', ')})`).toString();
137+
}
138+
139+
/**
140+
* Make an intrinsic States.ArrayGetItem expression
141+
*
142+
* Use this function to get a specified index's value in an array.
143+
*
144+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
145+
*/
146+
public static arrayGetItem(array: any, index: number): string {
147+
return new JsonPathToken(`States.ArrayGetItem(${[array, index].map(renderInExpression).join(', ')})`).toString();
148+
}
149+
150+
/**
151+
* Make an intrinsic States.ArrayLength expression
152+
*
153+
* Use this function to get the length of an array.
154+
*
155+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
156+
*/
157+
public static arrayLength(array: any): string {
158+
return new JsonPathToken(`States.ArrayLength(${renderInExpression(array)})`).toString();
159+
}
160+
161+
/**
162+
* Make an intrinsic States.ArrayUnique expression
163+
*
164+
* Use this function to get the length of an array.
165+
* 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.
166+
*
167+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
168+
*/
169+
public static arrayUnique(array: any): string {
170+
return new JsonPathToken(`States.ArrayUnique(${renderInExpression(array)})`).toString();
171+
}
172+
173+
/**
174+
* Make an intrinsic States.Base64Encode expression
175+
*
176+
* 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.
177+
*
178+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
179+
*/
180+
public static base64Encode(input: string): string {
181+
return new JsonPathToken(`States.Base64Encode(${renderInExpression(input)})`).toString();
182+
}
183+
184+
/**
185+
* Make an intrinsic States.Base64Decode expression
186+
*
187+
* 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.
188+
*
189+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
190+
*/
191+
public static base64Decode(base64: string): string {
192+
return new JsonPathToken(`States.Base64Decode(${renderInExpression(base64)})`).toString();
193+
}
194+
195+
/**
196+
* Make an intrinsic States.Hash expression
197+
*
198+
* 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.
199+
*
200+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
201+
*/
202+
public static hash(data: any, algorithm: string): string {
203+
return new JsonPathToken(`States.Hash(${[data, algorithm].map(renderInExpression).join(', ')})`).toString();
204+
}
205+
206+
/**
207+
* Make an intrinsic States.JsonMerge expression
208+
*
209+
* Use this function to merge two JSON objects into a single object.
210+
*
211+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
212+
*/
213+
public static jsonMerge(value1: any, value2: any): string {
214+
return new JsonPathToken(`States.JsonMerge(${[value1, value2].map(renderInExpression).join(', ')}, false)`).toString();
215+
}
216+
217+
/**
218+
* Make an intrinsic States.MathRandom expression
219+
*
220+
* 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.
221+
*
222+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
223+
*/
224+
public static mathRandom(start: number, end: number): string {
225+
return new JsonPathToken(`States.MathRandom(${[start, end].map(renderInExpression).join(', ')})`).toString();
226+
}
227+
228+
/**
229+
* Make an intrinsic States.MathAdd expression
230+
*
231+
* 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.
232+
*
233+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
234+
*/
235+
public static mathAdd(num1: number, num2: number): string {
236+
return new JsonPathToken(`States.MathAdd(${[num1, num2].map(renderInExpression).join(', ')})`).toString();
237+
}
238+
239+
/**
240+
* Make an intrinsic States.StringSplit expression
241+
*
242+
* 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.
243+
*
244+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
245+
*/
246+
public static stringSplit(inputString: string, splitter: string): string {
247+
return new JsonPathToken(`States.StringSplit(${[inputString, splitter].map(renderInExpression).join(', ')})`).toString();
248+
}
249+
250+
/**
251+
* Make an intrinsic States.UUID expression
252+
*
253+
* 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.
254+
*
255+
* @see https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html
256+
*/
257+
public static uuid(): string {
258+
return new JsonPathToken('States.UUID()').toString();
259+
}
260+
106261
/**
107262
* Make an intrinsic States.Format expression
108263
*
@@ -298,14 +453,45 @@ export class FieldUtils {
298453
}
299454

300455
function validateJsonPath(path: string) {
456+
const intrinsicFunctionNames = [
457+
// Intrinsics for arrays
458+
'Array',
459+
'ArrayPartition',
460+
'ArrayContains',
461+
'ArrayRange',
462+
'ArrayGetItem',
463+
'ArrayLength',
464+
'ArrayUnique',
465+
// Intrinsics for data encoding and decoding
466+
'Base64Encode',
467+
'Base64Decode',
468+
// Intrinsic for hash calculation
469+
'Hash',
470+
// Intrinsics for JSON data manipulation
471+
'JsonMerge',
472+
'StringToJson',
473+
'JsonToString',
474+
// Intrinsics for Math operations
475+
'MathRandom',
476+
'MathAdd',
477+
// Intrinsic for String operation
478+
'StringSplit',
479+
// Intrinsic for unique identifier generation
480+
'UUID',
481+
// Intrinsic for generic operation
482+
'Format',
483+
];
484+
const intrinsicFunctionFullNames = intrinsicFunctionNames.map((fn) => `States.${fn}`);
301485
if (path !== '$'
302486
&& !path.startsWith('$.')
303487
&& path !== '$$'
304488
&& !path.startsWith('$$.')
305489
&& !path.startsWith('$[')
306-
&& ['Format', 'StringToJson', 'JsonToString', 'Array'].every(fn => !path.startsWith(`States.${fn}`))
490+
&& intrinsicFunctionFullNames.every(fn => !path.startsWith(fn))
307491
) {
308-
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}`);
492+
const lastItem = intrinsicFunctionFullNames.pop();
493+
const intrinsicFunctionsStr = intrinsicFunctionFullNames.join(', ') + ', or ' + lastItem;
494+
throw new Error(`JSON path values must be exactly '$', '$$', start with '$.', start with '$$.', start with '$[', or start with an intrinsic function: ${intrinsicFunctionsStr}. Received: ${path}`);
309495
}
310496
}
311497

packages/@aws-cdk/aws-stepfunctions/lib/private/json-path.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,22 @@ function pathFromToken(token: IResolvable | undefined) {
312312
}
313313

314314
/**
315-
* Render the string in a valid JSON Path expression.
315+
* Render the string or number value in a valid JSON Path expression.
316316
*
317-
* If the string is a Tokenized JSON path reference -- return the JSON path reference inside it.
318-
* Otherwise, single-quote it.
317+
* If the value is a Tokenized JSON path reference -- return the JSON path reference inside it.
318+
* If the value is a number -- convert it to string.
319+
* If the value is a string -- single-quote it.
320+
* Otherwise, throw errors.
319321
*
320322
* Call this function whenever you're building compound JSONPath expressions, in
321323
* order to avoid having tokens-in-tokens-in-tokens which become very hard to parse.
322324
*/
323-
export function renderInExpression(x: string) {
324-
const path = jsonPathString(x);
325-
return path ?? singleQuotestring(x);
325+
export function renderInExpression(x: any) {
326+
const path = jsonPathFromAny(x);
327+
if (path) return path;
328+
if (typeof x === 'number') return x.toString(10);
329+
if (typeof x === 'string') return singleQuotestring(x);
330+
throw new Error('Unxexpected value.');
326331
}
327332

328333
function singleQuotestring(x: string) {
@@ -341,4 +346,4 @@ function singleQuotestring(x: string) {
341346
}
342347
ret.push("'");
343348
return ret.join('');
344-
}
349+
}

0 commit comments

Comments
 (0)