Skip to content

Commit

Permalink
feat(core): Fn::ToJsonString and Fn::Length intrinsic functions (#…
Browse files Browse the repository at this point in the history
…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*
  • Loading branch information
jogold authored Aug 30, 2022
1 parent 82ce4a1 commit 7472fa4
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 1 deletion.
9 changes: 9 additions & 0 deletions packages/@aws-cdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,9 @@ can be accessed from the `Fn` class, which provides type-safe methods for each
intrinsic function as well as condition expressions:

```ts
declare const myObjectOrArray: any;
declare const myArray: any;

// To use Fn::Base64
Fn.base64('SGVsbG8gQ0RLIQo=');

Expand All @@ -832,6 +835,12 @@ Fn.conditionAnd(
// The AWS::Region pseudo-parameter value is NOT equal to "us-east-1"
Fn.conditionNot(Fn.conditionEquals('us-east-1', Aws.REGION)),
);

// To use Fn::ToJsonString
Fn.toJsonString(myObjectOrArray);

// To use Fn::Length
Fn.len(Fn.split(',', myArray));
```

When working with deploy-time values (those for which `Token.isUnresolved`
Expand Down
90 changes: 89 additions & 1 deletion packages/@aws-cdk/core/lib/cfn-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { minimalCloudFormationJoin } from './private/cloudformation-lang';
import { Intrinsic } from './private/intrinsic';
import { Reference } from './reference';
import { IResolvable, IResolveContext } from './resolvable';
import { Stack } from './stack';
import { captureStackTrace } from './stack-trace';
import { Token } from './token';

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

/**
* The `Fn::ToJsonString` intrinsic function converts an object or array to its
* corresponding JSON string.
*
* @param object The object or array to stringify
*/
public static toJsonString(object: any): string {
// short-circut if object is not a token
if (!Token.isUnresolved(object)) {
return JSON.stringify(object);
}
return new FnToJsonString(object).toString();
}

/**
* The intrinsic function `Fn::Length` returns the number of elements within an array
* or an intrinsic function that returns an array.
*
* @param array The array you want to return the number of elements from
*/
public static len(array: any): number {
// short-circut if array is not a token
if (!Token.isUnresolved(array)) {
if (!Array.isArray(array)) {
throw new Error('Fn.length() needs an array');
}
return array.length;
}
return Token.asNumber(new FnLength(array));
}

private constructor() { }
}

Expand Down Expand Up @@ -829,6 +861,62 @@ class FnJoin implements IResolvable {
}
}

/**
* The `Fn::ToJsonString` intrinsic function converts an object or array to its
* corresponding JSON string.
*/
class FnToJsonString implements IResolvable {
public readonly creationStack: string[];

private readonly object: any;

constructor(object: any) {
this.object = object;
this.creationStack = captureStackTrace();
}

public resolve(context: IResolveContext): any {
Stack.of(context.scope).addTransform('AWS::LanguageExtensions');
return { 'Fn::ToJsonString': this.object };
}

public toString() {
return Token.asString(this, { displayHint: 'Fn::ToJsonString' });
}

public toJSON() {
return '<Fn::ToJsonString>';
}
}

/**
* The intrinsic function `Fn::Length` returns the number of elements within an array
* or an intrinsic function that returns an array.
*/
class FnLength implements IResolvable {
public readonly creationStack: string[];

private readonly array: any;

constructor(array: any) {
this.array = array;
this.creationStack = captureStackTrace();
}

public resolve(context: IResolveContext): any {
Stack.of(context.scope).addTransform('AWS::LanguageExtensions');
return { 'Fn::Length': this.array };
}

public toString() {
return Token.asString(this, { displayHint: 'Fn::Length' });
}

public toJSON() {
return '<Fn::Length>';
}
}

function _inGroupsOf<T>(array: T[], maxGroup: number): T[][] {
const result = new Array<T[]>();
for (let i = 0; i < array.length; i += maxGroup) {
Expand All @@ -843,4 +931,4 @@ function range(n: number): number[] {
ret.push(i);
}
return ret;
}
}
37 changes: 37 additions & 0 deletions packages/@aws-cdk/core/test/fn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,43 @@ test('Fn.importListValue produces lists of known length', () => {
]);
});

test('Fn.toJsonString', () => {
const stack = new Stack();
const token = Token.asAny({ key: 'value' });

expect(stack.resolve(Fn.toJsonString(token))).toEqual({ 'Fn::ToJsonString': { key: 'value' } });
expect(stack.templateOptions.transforms).toEqual(expect.arrayContaining([
'AWS::LanguageExtensions',
]));
});

test('Fn.toJsonString with resolved value', () => {
expect(Fn.toJsonString({ key: 'value' })).toEqual('{\"key\":\"value\"}');
});

test('Fn.len', () => {
const stack = new Stack();
const token = Fn.split('|', Token.asString({ ThisIsASplittable: 'list' }));

expect(stack.resolve(Fn.len(token))).toEqual({
'Fn::Length': {
'Fn::Split': [
'|',
{
ThisIsASplittable: 'list',
},
],
},
});
expect(stack.templateOptions.transforms).toEqual(expect.arrayContaining([
'AWS::LanguageExtensions',
]));
});

test('Fn.len with resolved value', () => {
expect(Fn.len(Fn.split('|', 'a|b|c'))).toBe(3);
});

function stringListToken(o: any): string[] {
return Token.asList(new Intrinsic(o));
}
Expand Down
9 changes: 9 additions & 0 deletions packages/aws-cdk-lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,9 @@ can be accessed from the `Fn` class, which provides type-safe methods for each
intrinsic function as well as condition expressions:

```ts
declare const myObjectOrArray: any;
declare const myArray: any;

// To use Fn::Base64
Fn.base64('SGVsbG8gQ0RLIQo=');

Expand All @@ -863,6 +866,12 @@ Fn.conditionAnd(
// The AWS::Region pseudo-parameter value is NOT equal to "us-east-1"
Fn.conditionNot(Fn.conditionEquals('us-east-1', Aws.REGION)),
);

// To use Fn::ToJsonString
Fn.toJsonString(myObjectOrArray);

// To use Fn::Length
Fn.len(Fn.split(',', myArray));
```

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

0 comments on commit 7472fa4

Please sign in to comment.