Skip to content

Commit dfe29ce

Browse files
authored
feat(stepfunctions): properties for ErrorPath, CausePath, MaxDelay, Jitter (#27051)
The Fail state supports JsonPath values in the `ErrorPath` and `CausePath` field to filter the state input and serve as the state error and cause. The Retry block supports exponential backoff with Jitter and capped interval retry growth with MaxDelay. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 641f9e8 commit dfe29ce

File tree

6 files changed

+104
-13
lines changed

6 files changed

+104
-13
lines changed

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ These properties impact how each individual step interacts with the state machin
128128
* `resultSelector`: the part of the step result that should be added to the state machine data
129129
* `resultPath`: where in the state machine data the step result should be inserted
130130
* `outputPath`: what part of the state machine data should be retained
131+
* `errorPath`: the part of the data object that gets returned as the step error
132+
* `causePath`: the part of the data object that gets returned as the step cause
131133

132134
Their values should be a string indicating a [JSON path](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-paths.html) into the State Machine Data object (like `"$.MyKey"`). If absent, the values are treated as if they were `"$"`, which means the entire object.
133135

@@ -421,8 +423,12 @@ parallel.branch(shipItem);
421423
parallel.branch(sendInvoice);
422424
parallel.branch(restock);
423425

424-
// Retry the whole workflow if something goes wrong
425-
parallel.addRetry({ maxAttempts: 1 });
426+
// Retry the whole workflow if something goes wrong with exponential backoff
427+
parallel.addRetry({
428+
maxAttempts: 1,
429+
maxDelay: Duration.seconds(5),
430+
jitterStrategy: sfn.JitterType.FULL,
431+
});
426432

427433
// How to recover from errors
428434
const sendFailureNotification = new sfn.Pass(this, 'SendFailureNotification');
@@ -449,12 +455,21 @@ failure status. The fail state should report the reason for the failure.
449455
Failures can be caught by encompassing `Parallel` states.
450456

451457
```ts
452-
const success = new sfn.Fail(this, 'Fail', {
458+
const fail = new sfn.Fail(this, 'Fail', {
453459
error: 'WorkflowFailure',
454460
cause: "Something went wrong",
455461
});
456462
```
457463

464+
The `Fail` state also supports returning dynamic values as the error and cause that are selected from the input with a path.
465+
466+
```ts
467+
const fail = new sfn.Fail(this, 'Fail', {
468+
errorPath: sfn.JsonPath.stringAt('$.someError'),
469+
causePath: sfn.JsonPath.stringAt('$.someCause'),
470+
});
471+
```
472+
458473
### Map
459474

460475
A `Map` state can be used to run a set of steps for each element of an input array.
@@ -961,12 +976,12 @@ const stack = new Stack(app, 'MyStack');
961976
sfn.StateMachine.fromStateMachineArn(
962977
this,
963978
"ViaArnImportedStateMachine",
964-
"arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ"
979+
"arn:aws:states:us-east-1:123456789012:stateMachine:StateMachine2E01A3A5-N5TJppzoevKQ",
965980
);
966981

967982
sfn.StateMachine.fromStateMachineName(
968983
this,
969984
"ViaResourceNameImportedStateMachine",
970-
"StateMachine2E01A3A5-N5TJppzoevKQ"
985+
"StateMachine2E01A3A5-N5TJppzoevKQ",
971986
);
972987
```

packages/aws-cdk-lib/aws-stepfunctions/lib/states/fail.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Construct } from 'constructs';
22
import { StateType } from './private/state-type';
3-
import { State } from './state';
3+
import { renderJsonPath, State } from './state';
44
import { INextable } from '../types';
55

66
/**
@@ -10,23 +10,37 @@ export interface FailProps {
1010
/**
1111
* An optional description for this state
1212
*
13-
* @default No comment
13+
* @default - No comment
1414
*/
1515
readonly comment?: string;
1616

1717
/**
1818
* Error code used to represent this failure
1919
*
20-
* @default No error code
20+
* @default - No error code
2121
*/
2222
readonly error?: string;
2323

24+
/**
25+
* JsonPath expression to select part of the state to be the error to this state.
26+
*
27+
* @default - No error path
28+
*/
29+
readonly errorPath?: string;
30+
2431
/**
2532
* A description for the cause of the failure
2633
*
27-
* @default No description
34+
* @default - No description
2835
*/
2936
readonly cause?: string;
37+
38+
/**
39+
* JsonPath expression to select part of the state to be the cause to this state.
40+
*
41+
* @default - No cause path
42+
*/
43+
readonly causePath?: string;
3044
}
3145

3246
/**
@@ -38,13 +52,17 @@ export class Fail extends State {
3852
public readonly endStates: INextable[] = [];
3953

4054
private readonly error?: string;
55+
private readonly errorPath?: string;
4156
private readonly cause?: string;
57+
private readonly causePath?: string;
4258

4359
constructor(scope: Construct, id: string, props: FailProps = {}) {
4460
super(scope, id, props);
4561

4662
this.error = props.error;
63+
this.errorPath = props.errorPath;
4764
this.cause = props.cause;
65+
this.causePath = props.causePath;
4866
}
4967

5068
/**
@@ -55,7 +73,9 @@ export class Fail extends State {
5573
Type: StateType.FAIL,
5674
Comment: this.comment,
5775
Error: this.error,
76+
ErrorPath: renderJsonPath(this.errorPath),
5877
Cause: this.cause,
78+
CausePath: renderJsonPath(this.causePath),
5979
};
6080
}
61-
}
81+
}

packages/aws-cdk-lib/aws-stepfunctions/lib/states/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,8 @@ function renderRetry(retry: RetryProps) {
525525
IntervalSeconds: retry.interval && retry.interval.toSeconds(),
526526
MaxAttempts: retry.maxAttempts,
527527
BackoffRate: retry.backoffRate,
528+
MaxDelaySeconds: retry.maxDelay && retry.maxDelay.toSeconds(),
529+
JitterStrategy: retry.jitterStrategy,
528530
};
529531
}
530532

packages/aws-cdk-lib/aws-stepfunctions/lib/types.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ export interface IChainable {
3434
readonly endStates: INextable[];
3535
}
3636

37+
/**
38+
* Values allowed in the retrier JitterStrategy field
39+
*/
40+
export enum JitterType {
41+
/**
42+
* Calculates the delay to be a random number between 0 and the computed backoff for the given retry attempt count
43+
*/
44+
FULL = 'FULL',
45+
46+
/**
47+
* Calculates the delay to be the computed backoff for the given retry attempt count (equivalent to if Jitter was not declared - i.e. the default value)
48+
*/
49+
NONE = 'NONE',
50+
}
51+
3752
/**
3853
* Predefined error strings
3954
* Error names in Amazon States Language - https://states-language.net/spec.html#appendix-a
@@ -121,6 +136,20 @@ export interface RetryProps {
121136
*/
122137
readonly maxAttempts?: number;
123138

139+
/**
140+
* Maximum limit on retry interval growth during exponential backoff.
141+
*
142+
* @default - No max delay
143+
*/
144+
readonly maxDelay?: Duration;
145+
146+
/**
147+
* Introduces a randomization over the retry interval.
148+
*
149+
* @default - No jitter strategy
150+
*/
151+
readonly jitterStrategy?: JitterType;
152+
124153
/**
125154
* Multiplication for how much longer the wait interval gets on every retry
126155
*
@@ -158,4 +187,4 @@ export interface CatchProps {
158187
* Special string value to discard state input, output or result
159188
* @deprecated use JsonPath.DISCARD
160189
*/
161-
export const DISCARD = 'DISCARD';
190+
export const DISCARD = 'DISCARD';

packages/aws-cdk-lib/aws-stepfunctions/test/state-machine-resources.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ describe('State Machine Resources', () => {
7777
});
7878
}),
7979

80+
test('Fail should render ErrorPath / CausePath correctly', () => {
81+
// GIVEN
82+
const stack = new cdk.Stack();
83+
const fail = new stepfunctions.Fail(stack, 'Fail', {
84+
errorPath: stepfunctions.JsonPath.stringAt('$.error'),
85+
causePath: stepfunctions.JsonPath.stringAt('$.cause'),
86+
});
87+
88+
// WHEN
89+
const failState = stack.resolve(fail.toStateJson());
90+
91+
// THEN
92+
expect(failState).toStrictEqual({
93+
CausePath: '$.cause',
94+
ErrorPath: '$.error',
95+
Type: 'Fail',
96+
});
97+
}),
98+
8099
testDeprecated('Task should render InputPath / Parameters / OutputPath correctly', () => {
81100
// GIVEN
82101
const stack = new cdk.Stack();

packages/aws-cdk-lib/aws-stepfunctions/test/task-base.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,12 @@ describe('Task base', () => {
229229

230230
test('add retry configuration', () => {
231231
// WHEN
232-
task.addRetry({ errors: ['HTTPError'], maxAttempts: 2 })
233-
.addRetry(); // adds default retry
232+
task.addRetry({
233+
errors: ['HTTPError'],
234+
maxAttempts: 2,
235+
maxDelay: cdk.Duration.seconds(10),
236+
jitterStrategy: sfn.JitterType.FULL,
237+
}).addRetry();
234238

235239
// THEN
236240
expect(renderGraph(task)).toEqual({
@@ -242,6 +246,8 @@ describe('Task base', () => {
242246
{
243247
ErrorEquals: ['HTTPError'],
244248
MaxAttempts: 2,
249+
MaxDelaySeconds: 10,
250+
JitterStrategy: 'FULL',
245251
},
246252
{
247253
ErrorEquals: ['States.ALL'],

0 commit comments

Comments
 (0)