From 62246f54f01414989a616da90dfa112ecfde3593 Mon Sep 17 00:00:00 2001 From: Grayden Shand Date: Mon, 18 Dec 2023 21:54:13 -0500 Subject: [PATCH 1/5] Add addCatch method to CustomState --- .../aws-stepfunctions/lib/states/custom-state.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts b/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts index 3bf14fc249b80..55dd0f1c0a445 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { State } from './state'; import { Chain } from '..'; -import { IChainable, INextable } from '../types'; +import { CatchProps, IChainable, INextable } from '../types'; /** * Properties for defining a custom state definition @@ -34,6 +34,17 @@ export class CustomState extends State implements IChainable, INextable { this.stateJson = props.stateJson; } + /** + * Add a recovery handler for this state + * + * When a particular error occurs, execution will continue at the error + * handler instead of failing the state machine execution. + */ + public addCatch(handler: IChainable, props: CatchProps = {}): CustomState { + super._addCatch(handler.startState, props); + return this; + } + /** * Continue normal execution with the given state */ From 87fd91a5485130a637916460f59ca68ea01060d6 Mon Sep 17 00:00:00 2001 From: Grayden Shand Date: Mon, 18 Dec 2023 22:27:06 -0500 Subject: [PATCH 2/5] Add test case and documentation --- .../aws-cdk-lib/aws-stepfunctions/README.md | 4 ++ .../test/custom-state.test.ts | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/aws-cdk-lib/aws-stepfunctions/README.md b/packages/aws-cdk-lib/aws-stepfunctions/README.md index ab142c0f1ddbd..aef1e2ffaa631 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/README.md +++ b/packages/aws-cdk-lib/aws-stepfunctions/README.md @@ -598,6 +598,10 @@ const custom = new sfn.CustomState(this, 'my custom task', { stateJson, }); +// catch errors with addCatch +const errorHandler = new sfn.Pass(this, 'handle failure'); +custom.addCatch(errorHandler); + const chain = sfn.Chain.start(custom) .next(finalStatus); diff --git a/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts b/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts index 98ccf387bad49..2e7b241af051e 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts @@ -72,4 +72,41 @@ describe('Custom State', () => { }, ); }); + + test('can add a catch state to thechain'), () => { + // WHEN + const fail = new sfn.Fail(stack, 'MyFail', { error: 'DidNotWork', cause: 'HumanError' }) + const definition = new sfn.CustomState(stack, 'Custom', { + stateJson, + }).addCatch(fail); + + // THEN + expect(render(stack, definition)).toStrictEqual( + { + StartAt: 'Custom', + States: { + 'Custom': { + Type: 'Task', + Resource: 'arn:aws:states:::dynamodb:putItem', + Parameters: { + TableName: 'MyTable', + Item: { + id: { + S: 'MyEntry', + }, + }, + }, + ResultPath: null, + Catch: { + + } + }, + 'my-pass-state': { + Type: 'Pass', + End: true, + }, + }, + }, + ); + }; }); \ No newline at end of file From f544bbf3c1638933b40ef51b5ea95e64a088221a Mon Sep 17 00:00:00 2001 From: Grayden Shand Date: Mon, 18 Dec 2023 22:57:08 -0500 Subject: [PATCH 3/5] passing tests --- .../lib/states/custom-state.ts | 3 +- .../test/custom-state.test.ts | 57 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts b/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts index 55dd0f1c0a445..db66a5a8de17f 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions/lib/states/custom-state.ts @@ -25,7 +25,7 @@ export class CustomState extends State implements IChainable, INextable { /** * Amazon States Language (JSON-based) definition of the state */ - private readonly stateJson: { [key: string]: any}; + private readonly stateJson: { [key: string]: any }; constructor(scope: Construct, id: string, props: CustomStateProps) { super(scope, id, {}); @@ -60,6 +60,7 @@ export class CustomState extends State implements IChainable, INextable { return { ...this.renderNextEnd(), ...this.stateJson, + ...this.renderRetryCatch(), }; } } diff --git a/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts b/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts index 2e7b241af051e..766b8ac659abe 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions/test/custom-state.test.ts @@ -33,6 +33,7 @@ describe('Custom State', () => { // THEN expect(customState.toStateJson()).toStrictEqual({ ...stateJson, + ...{ Catch: undefined, Retry: undefined }, End: true, }); }); @@ -73,40 +74,50 @@ describe('Custom State', () => { ); }); - test('can add a catch state to thechain'), () => { + test('can add a catch state', () => { + // GIVEN + const failure = new sfn.Fail(stack, 'failed', { + error: 'DidNotWork', + cause: 'We got stuck', + }); + const custom = new sfn.CustomState(stack, 'Custom', { + stateJson, + }); + const chain = sfn.Chain.start(custom); + // WHEN - const fail = new sfn.Fail(stack, 'MyFail', { error: 'DidNotWork', cause: 'HumanError' }) - const definition = new sfn.CustomState(stack, 'Custom', { - stateJson, - }).addCatch(fail); - + custom.addCatch(failure); + // THEN - expect(render(stack, definition)).toStrictEqual( - { + expect(render(stack, chain)).toStrictEqual( + { StartAt: 'Custom', States: { - 'Custom': { + Custom: { Type: 'Task', Resource: 'arn:aws:states:::dynamodb:putItem', Parameters: { - TableName: 'MyTable', - Item: { - id: { - S: 'MyEntry', - }, + TableName: 'MyTable', + Item: { + id: { + S: 'MyEntry', }, + }, }, ResultPath: null, - Catch: { - - } - }, - 'my-pass-state': { - Type: 'Pass', + Catch: [{ + ErrorEquals: ['States.ALL'], + Next: 'failed', + }], End: true, + }, + failed: { + Type: 'Fail', + Error: 'DidNotWork', + Cause: 'We got stuck', + }, }, - }, - }, + }, ); - }; + }); }); \ No newline at end of file From 4edf7f788177694a169cacae7842122a354f14b7 Mon Sep 17 00:00:00 2001 From: Grayden Shand Date: Mon, 18 Dec 2023 23:01:48 -0500 Subject: [PATCH 4/5] Add to customstate integration test --- .../test/aws-stepfunctions/test/integ.custom-state.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.ts index d65b0e373945b..5d28b12436975 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.ts @@ -25,10 +25,17 @@ const stateJson = { ResultPath: null, }; +const failure = new sfn.Fail(stack, 'failed', { + error: 'DidNotWork', + cause: 'We got stuck', +}); + const custom = new sfn.CustomState(stack, 'my custom task', { stateJson, }); +custom.addCatch(failure); + const chain = sfn.Chain.start(custom).next(finalStatus); const sm = new sfn.StateMachine(stack, 'StateMachine', { From a47da2801c01d8b4bd8f0fbf73761a20cf487249 Mon Sep 17 00:00:00 2001 From: Grayden Shand Date: Mon, 18 Dec 2023 23:39:37 -0500 Subject: [PATCH 5/5] Add updated snapshot --- .../aws-stepfunctions-custom-state-integ.template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.js.snapshot/aws-stepfunctions-custom-state-integ.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.js.snapshot/aws-stepfunctions-custom-state-integ.template.json index fde50d40dc7b7..b090502f0294b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.js.snapshot/aws-stepfunctions-custom-state-integ.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions/test/integ.custom-state.js.snapshot/aws-stepfunctions-custom-state-integ.template.json @@ -26,7 +26,7 @@ "Arn" ] }, - "DefinitionString": "{\"StartAt\":\"my custom task\",\"States\":{\"my custom task\":{\"Next\":\"final step\",\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::dynamodb:putItem\",\"Parameters\":{\"TableName\":\"my-cool-table\",\"Item\":{\"id\":{\"S\":\"my-entry\"}}},\"ResultPath\":null},\"final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" + "DefinitionString": "{\"StartAt\":\"my custom task\",\"States\":{\"my custom task\":{\"Next\":\"final step\",\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::dynamodb:putItem\",\"Parameters\":{\"TableName\":\"my-cool-table\",\"Item\":{\"id\":{\"S\":\"my-entry\"}}},\"ResultPath\":null,\"Catch\":[{\"ErrorEquals\":[\"States.ALL\"],\"Next\":\"failed\"}]},\"final step\":{\"Type\":\"Pass\",\"End\":true},\"failed\":{\"Type\":\"Fail\",\"Error\":\"DidNotWork\",\"Cause\":\"We got stuck\"}},\"TimeoutSeconds\":30}" }, "DependsOn": [ "StateMachineRoleB840431D"