Skip to content

Commit 7472fa4

Browse files
authored
feat(core): Fn::ToJsonString and Fn::Length intrinsic functions (#21749)
Add support for `Fn::ToJsonString` and `Fn::Length`. The `AWS::LanguageExtensions` transform is automatically added. See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ToJsonString.html See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-length.html See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-transform.html ---- ### 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 * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] 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 82ce4a1 commit 7472fa4

File tree

4 files changed

+144
-1
lines changed

4 files changed

+144
-1
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,9 @@ can be accessed from the `Fn` class, which provides type-safe methods for each
821821
intrinsic function as well as condition expressions:
822822

823823
```ts
824+
declare const myObjectOrArray: any;
825+
declare const myArray: any;
826+
824827
// To use Fn::Base64
825828
Fn.base64('SGVsbG8gQ0RLIQo=');
826829

@@ -832,6 +835,12 @@ Fn.conditionAnd(
832835
// The AWS::Region pseudo-parameter value is NOT equal to "us-east-1"
833836
Fn.conditionNot(Fn.conditionEquals('us-east-1', Aws.REGION)),
834837
);
838+
839+
// To use Fn::ToJsonString
840+
Fn.toJsonString(myObjectOrArray);
841+
842+
// To use Fn::Length
843+
Fn.len(Fn.split(',', myArray));
835844
```
836845

837846
When working with deploy-time values (those for which `Token.isUnresolved`

packages/@aws-cdk/core/lib/cfn-fn.ts

+89-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { minimalCloudFormationJoin } from './private/cloudformation-lang';
33
import { Intrinsic } from './private/intrinsic';
44
import { Reference } from './reference';
55
import { IResolvable, IResolveContext } from './resolvable';
6+
import { Stack } from './stack';
67
import { captureStackTrace } from './stack-trace';
78
import { Token } from './token';
89

@@ -412,6 +413,37 @@ export class Fn {
412413
return Token.asList(new FnValueOfAll(parameterType, attribute));
413414
}
414415

416+
/**
417+
* The `Fn::ToJsonString` intrinsic function converts an object or array to its
418+
* corresponding JSON string.
419+
*
420+
* @param object The object or array to stringify
421+
*/
422+
public static toJsonString(object: any): string {
423+
// short-circut if object is not a token
424+
if (!Token.isUnresolved(object)) {
425+
return JSON.stringify(object);
426+
}
427+
return new FnToJsonString(object).toString();
428+
}
429+
430+
/**
431+
* The intrinsic function `Fn::Length` returns the number of elements within an array
432+
* or an intrinsic function that returns an array.
433+
*
434+
* @param array The array you want to return the number of elements from
435+
*/
436+
public static len(array: any): number {
437+
// short-circut if array is not a token
438+
if (!Token.isUnresolved(array)) {
439+
if (!Array.isArray(array)) {
440+
throw new Error('Fn.length() needs an array');
441+
}
442+
return array.length;
443+
}
444+
return Token.asNumber(new FnLength(array));
445+
}
446+
415447
private constructor() { }
416448
}
417449

@@ -829,6 +861,62 @@ class FnJoin implements IResolvable {
829861
}
830862
}
831863

864+
/**
865+
* The `Fn::ToJsonString` intrinsic function converts an object or array to its
866+
* corresponding JSON string.
867+
*/
868+
class FnToJsonString implements IResolvable {
869+
public readonly creationStack: string[];
870+
871+
private readonly object: any;
872+
873+
constructor(object: any) {
874+
this.object = object;
875+
this.creationStack = captureStackTrace();
876+
}
877+
878+
public resolve(context: IResolveContext): any {
879+
Stack.of(context.scope).addTransform('AWS::LanguageExtensions');
880+
return { 'Fn::ToJsonString': this.object };
881+
}
882+
883+
public toString() {
884+
return Token.asString(this, { displayHint: 'Fn::ToJsonString' });
885+
}
886+
887+
public toJSON() {
888+
return '<Fn::ToJsonString>';
889+
}
890+
}
891+
892+
/**
893+
* The intrinsic function `Fn::Length` returns the number of elements within an array
894+
* or an intrinsic function that returns an array.
895+
*/
896+
class FnLength implements IResolvable {
897+
public readonly creationStack: string[];
898+
899+
private readonly array: any;
900+
901+
constructor(array: any) {
902+
this.array = array;
903+
this.creationStack = captureStackTrace();
904+
}
905+
906+
public resolve(context: IResolveContext): any {
907+
Stack.of(context.scope).addTransform('AWS::LanguageExtensions');
908+
return { 'Fn::Length': this.array };
909+
}
910+
911+
public toString() {
912+
return Token.asString(this, { displayHint: 'Fn::Length' });
913+
}
914+
915+
public toJSON() {
916+
return '<Fn::Length>';
917+
}
918+
}
919+
832920
function _inGroupsOf<T>(array: T[], maxGroup: number): T[][] {
833921
const result = new Array<T[]>();
834922
for (let i = 0; i < array.length; i += maxGroup) {
@@ -843,4 +931,4 @@ function range(n: number): number[] {
843931
ret.push(i);
844932
}
845933
return ret;
846-
}
934+
}

packages/@aws-cdk/core/test/fn.test.ts

+37
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,43 @@ test('Fn.importListValue produces lists of known length', () => {
266266
]);
267267
});
268268

269+
test('Fn.toJsonString', () => {
270+
const stack = new Stack();
271+
const token = Token.asAny({ key: 'value' });
272+
273+
expect(stack.resolve(Fn.toJsonString(token))).toEqual({ 'Fn::ToJsonString': { key: 'value' } });
274+
expect(stack.templateOptions.transforms).toEqual(expect.arrayContaining([
275+
'AWS::LanguageExtensions',
276+
]));
277+
});
278+
279+
test('Fn.toJsonString with resolved value', () => {
280+
expect(Fn.toJsonString({ key: 'value' })).toEqual('{\"key\":\"value\"}');
281+
});
282+
283+
test('Fn.len', () => {
284+
const stack = new Stack();
285+
const token = Fn.split('|', Token.asString({ ThisIsASplittable: 'list' }));
286+
287+
expect(stack.resolve(Fn.len(token))).toEqual({
288+
'Fn::Length': {
289+
'Fn::Split': [
290+
'|',
291+
{
292+
ThisIsASplittable: 'list',
293+
},
294+
],
295+
},
296+
});
297+
expect(stack.templateOptions.transforms).toEqual(expect.arrayContaining([
298+
'AWS::LanguageExtensions',
299+
]));
300+
});
301+
302+
test('Fn.len with resolved value', () => {
303+
expect(Fn.len(Fn.split('|', 'a|b|c'))).toBe(3);
304+
});
305+
269306
function stringListToken(o: any): string[] {
270307
return Token.asList(new Intrinsic(o));
271308
}

packages/aws-cdk-lib/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,9 @@ can be accessed from the `Fn` class, which provides type-safe methods for each
852852
intrinsic function as well as condition expressions:
853853

854854
```ts
855+
declare const myObjectOrArray: any;
856+
declare const myArray: any;
857+
855858
// To use Fn::Base64
856859
Fn.base64('SGVsbG8gQ0RLIQo=');
857860

@@ -863,6 +866,12 @@ Fn.conditionAnd(
863866
// The AWS::Region pseudo-parameter value is NOT equal to "us-east-1"
864867
Fn.conditionNot(Fn.conditionEquals('us-east-1', Aws.REGION)),
865868
);
869+
870+
// To use Fn::ToJsonString
871+
Fn.toJsonString(myObjectOrArray);
872+
873+
// To use Fn::Length
874+
Fn.len(Fn.split(',', myArray));
866875
```
867876

868877
When working with deploy-time values (those for which `Token.isUnresolved`

0 commit comments

Comments
 (0)