Skip to content

Commit 2d4d911

Browse files
committed
feat(1091): allow catch on any task
1 parent 0ceb0e3 commit 2d4d911

7 files changed

+474
-40
lines changed

ctk/features/try.feature

+124-1
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,127 @@ Feature: Try Task
8484
petName: Milou
8585
"""
8686
When the workflow is executed
87-
Then the workflow should fault
87+
Then the workflow should fault
88+
89+
# Tests task-level catch functionality
90+
# Tests error handling without try block
91+
# Tests custom error variable name
92+
# Tests error instance path
93+
Scenario: Task Level Catch Handle Error
94+
Given a workflow with definition:
95+
"""yaml
96+
document:
97+
dsl: '1.0.0'
98+
namespace: default
99+
name: task-level-catch
100+
version: '1.0.0'
101+
do:
102+
- getPet:
103+
call: http
104+
with:
105+
method: get
106+
endpoint:
107+
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
108+
catch:
109+
errors:
110+
with:
111+
type: https://serverlessworkflow.io/dsl/errors/types/communication
112+
status: 404
113+
as: taskError
114+
do:
115+
- handleError:
116+
set:
117+
error: ${ $taskError }
118+
"""
119+
And given the workflow input is:
120+
"""yaml
121+
petName: Milou
122+
"""
123+
When the workflow is executed
124+
Then the workflow should complete
125+
And the workflow output should have properties 'error', 'error.type', 'error.status', 'error.title'
126+
And the workflow output should have a 'error.instance' property with value:
127+
"""yaml
128+
/do/0/getPet
129+
"""
130+
131+
# Tests task-level catch with retry
132+
# Tests retry policy configuration
133+
# Tests error handling
134+
Scenario: Task Level Catch With Retry
135+
Given a workflow with definition:
136+
"""yaml
137+
document:
138+
dsl: '1.0.0'
139+
namespace: default
140+
name: task-level-catch-retry
141+
version: '1.0.0'
142+
do:
143+
- getPet:
144+
call: http
145+
with:
146+
method: get
147+
endpoint:
148+
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
149+
catch:
150+
errors:
151+
with:
152+
type: https://serverlessworkflow.io/dsl/errors/types/communication
153+
status: 503
154+
retry:
155+
delay:
156+
seconds: 2
157+
backoff:
158+
exponential: {}
159+
limit:
160+
attempt:
161+
count: 3
162+
do:
163+
- handleError:
164+
set:
165+
status: "service_unavailable"
166+
"""
167+
And given the workflow input is:
168+
"""yaml
169+
petName: Milou
170+
"""
171+
When the workflow is executed
172+
Then the workflow should complete
173+
And the workflow output should have a 'status' property with value 'service_unavailable'
174+
175+
# Tests task-level catch with conditional handling
176+
# Tests when condition in catch
177+
# Tests error variable access
178+
Scenario: Task Level Catch With Condition
179+
Given a workflow with definition:
180+
"""yaml
181+
document:
182+
dsl: '1.0.0'
183+
namespace: default
184+
name: task-level-catch-condition
185+
version: '1.0.0'
186+
do:
187+
- getPet:
188+
call: http
189+
with:
190+
method: get
191+
endpoint:
192+
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
193+
catch:
194+
errors:
195+
with:
196+
type: https://serverlessworkflow.io/dsl/errors/types/communication
197+
when: $error.status == 404
198+
as: notFoundError
199+
do:
200+
- handleNotFound:
201+
set:
202+
message: "Pet not found"
203+
"""
204+
And given the workflow input is:
205+
"""yaml
206+
petName: Milou
207+
"""
208+
When the workflow is executed
209+
Then the workflow should complete
210+
And the workflow output should have a 'message' property with value 'Pet not found'

dsl-reference.md

+37-1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ The Serverless Workflow DSL defines a list of [tasks](#task) that **must be** su
285285
| export | [`export`](#export) | `no` | An object used to customize the content of the workflow context. |
286286
| timeout | `string`<br>[`timeout`](#timeout) | `no` | The configuration of the task's timeout, if any.<br>*If a `string`, must be the name of a [timeout](#timeout) defined in the [workflow's reusable components](#use).* |
287287
| then | [`flowDirective`](#flow-directive) | `no` | The flow directive to execute next.<br>*If not set, defaults to `continue`.* |
288+
| catch | [`catch`](#catch) | `no` | Used to handle errors and define retry policies for the current task. See [Catch](#catch) for detailed documentation. |
288289
| metadata | `map` | `no` | Additional information about the task. |
289290

290291
#### Call
@@ -1111,7 +1112,12 @@ do:
11111112

11121113
##### Catch
11131114

1114-
Defines the configuration of a catch clause, which a concept used to catch errors.
1115+
Defines the configuration of a catch clause, which is a concept used to catch errors and to define what should happen when they occur.
1116+
Catch nodes can be used in two ways:
1117+
1. With any [`task`](#task) to handle errors on that single task
1118+
2. With the [`try`](#try) task to handle errors on a group of tasks
1119+
1120+
This flexibility allows for both fine-grained error handling at the task level and broader error handling for groups of tasks.
11151121

11161122
###### Properties
11171123

@@ -1124,6 +1130,36 @@ Defines the configuration of a catch clause, which a concept used to catch error
11241130
| retry | `string`<br>[`retryPolicy`](#retry) | `no` | The [`retry policy`](#retry) to use, if any, when catching [`errors`](#error).<br>*If a `string`, must be the name of a [retry policy](#retry) defined in the [workflow's reusable components](#use).* |
11251131
| do | [`map[string, task]`](#task) | `no` | The definition of the task(s) to run when catching an error. |
11261132

1133+
##### Examples
1134+
1135+
```yaml
1136+
document:
1137+
dsl: '1.0.0'
1138+
namespace: test
1139+
name: catch-example
1140+
version: '0.1.0'
1141+
do:
1142+
- invalidHttpCall:
1143+
call: http
1144+
with:
1145+
method: get
1146+
endpoint: https://
1147+
catch:
1148+
errors:
1149+
with:
1150+
type: https://serverlessworkflow.io.io/dsl/errors/types/communication
1151+
status: 503
1152+
as: error
1153+
retry:
1154+
delay:
1155+
seconds: 3
1156+
backoff:
1157+
exponential: {}
1158+
limit:
1159+
attempt:
1160+
count: 5
1161+
```
1162+
11271163
#### Wait
11281164

11291165
Allows workflows to pause or delay their execution for a specified period of time.

dsl.md

+97-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ See the [DSL reference](dsl-reference.md#error) for more details about errors.
483483
484484
#### Retries
485485
486-
Errors are critical for both authors and runtimes as they provide a means to communicate and describe the occurrence of problems. This, in turn, enables the definition of mechanisms to catch and act upon these errors. For instance, some errors caught using a [`try`](dsl-reference.md#try) block may be transient and temporary, such as a `503 Service Unavailable`. In such cases, the DSL provides a mechanism to retry a faulted task, allowing for recovery from temporary issues.
486+
Errors are critical for both authors and runtimes as they provide a means to communicate and describe the occurrence of problems. This, in turn, enables the definition of mechanisms to catch and act upon these errors. For instance, some errors may be transient and temporary, such as a `503 Service Unavailable`. In such cases, the DSL provides a mechanism to retry a [`faulted task`](dsl-reference.md#task), or a [`group of tasks`](dsl-reference.md#try), allowing for recovery from temporary issues.
487487

488488
*Retrying 5 times when an error with 503 is caught:*
489489
```yaml
@@ -507,6 +507,102 @@ catch:
507507
count: 5
508508
```
509509

510+
#### Task-Level Error Handling
511+
512+
In addition to the try-catch mechanism, any task can define its own error handling using the `catch` property. This allows for more granular error handling at the task level without the need to wrap the task in a try block.
513+
514+
Task-level error handling provides several benefits:
515+
1. More concise and readable code by avoiding nested try blocks
516+
2. Direct association of error handling with the task that might fail
517+
3. Reusability of error handling logic when the task is used as a function
518+
519+
*Example of task-level error handling:*
520+
```yaml
521+
do:
522+
- getPetById:
523+
call: http
524+
with:
525+
method: get
526+
endpoint:
527+
uri: https://petstore.swagger.io/v2/pet/{id}
528+
catch:
529+
errors:
530+
with:
531+
status: 404
532+
do:
533+
- createDefaultPet:
534+
call: http
535+
with:
536+
method: post
537+
endpoint:
538+
uri: https://petstore.swagger.io/v2/pet
539+
body:
540+
id: ${ .id }
541+
name: "Default Pet"
542+
status: "available"
543+
```
544+
545+
*Example comparing try-catch with task-level catch:*
546+
```yaml
547+
# Using try-catch
548+
do:
549+
- handlePet:
550+
try:
551+
call: http
552+
with:
553+
method: get
554+
endpoint:
555+
uri: https://petstore.swagger.io/v2/pet/{id}
556+
catch:
557+
errors:
558+
with:
559+
status: 404
560+
do:
561+
- createDefaultPet:
562+
call: http
563+
with:
564+
method: post
565+
endpoint:
566+
uri: https://petstore.swagger.io/v2/pet
567+
body:
568+
id: ${ .id }
569+
name: "Default Pet"
570+
status: "available"
571+
572+
# Using task-level catch (more concise)
573+
do:
574+
- getPetById:
575+
call: http
576+
with:
577+
method: get
578+
endpoint:
579+
uri: https://petstore.swagger.io/v2/pet/{id}
580+
catch:
581+
errors:
582+
with:
583+
status: 404
584+
do:
585+
- createDefaultPet:
586+
call: http
587+
with:
588+
method: post
589+
endpoint:
590+
uri: https://petstore.swagger.io/v2/pet
591+
body:
592+
id: ${ .id }
593+
name: "Default Pet"
594+
status: "available"
595+
```
596+
597+
Task-level error handling supports all the same features as try-catch:
598+
- Error filtering with `with`
599+
- Error variable naming with `as`
600+
- Conditional catching with `when` and `exceptWhen`
601+
- Retry policies
602+
- Task list execution with `do`
603+
604+
See the [DSL reference](dsl-reference.md#task-catch) for more details about task-level error handling.
605+
510606
### Timeouts
511607

512608
Workflows and tasks alike can be configured to timeout after a defined amount of time.

examples/task-level-catch-basic.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
document:
2+
dsl: '1.0.0'
3+
namespace: examples
4+
name: task-level-catch-basic
5+
version: '1.0.0'
6+
description: |
7+
This example demonstrates basic task-level error handling using catch.
8+
It shows how to handle errors directly on individual tasks without using a try block.
9+
10+
do:
11+
- failingTask:
12+
call: custom
13+
with:
14+
function: simulateFailure
15+
catch:
16+
errors:
17+
with:
18+
type: https://serverlessworkflow.io/dsl/errors/types/custom
19+
as: taskError
20+
do:
21+
handleError:
22+
set:
23+
value: ${ { "error": taskError, "message": "Handled error at task level" } }
24+
25+
- successTask:
26+
set:
27+
value: "Task completed successfully after error handling"
28+
29+
functions:
30+
- name: simulateFailure
31+
type: custom
32+
operation: fake.operation

examples/task-level-catch-http.yaml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
document:
2+
dsl: '1.0.0'
3+
namespace: examples
4+
name: task-level-catch-http
5+
version: '1.0.0'
6+
description: |
7+
This example demonstrates task-level error handling with HTTP calls.
8+
It shows how to handle common HTTP errors like timeouts and server errors,
9+
including retry logic for transient failures.
10+
11+
do:
12+
- callExternalService:
13+
call: http
14+
with:
15+
method: GET
16+
endpoint: https://api.example.com/potentially-unstable-service
17+
catch:
18+
errors:
19+
with:
20+
type: https://serverlessworkflow.io/dsl/errors/types/communication
21+
status: 503
22+
retry:
23+
delay:
24+
seconds: 2
25+
backoff:
26+
exponential: {}
27+
limit:
28+
attempt:
29+
count: 3
30+
do:
31+
handleServiceUnavailable:
32+
set:
33+
value: ${ { "status": "service_unavailable", "message": "External service temporarily unavailable" } }
34+
35+
- callAnotherEndpoint:
36+
call: http
37+
with:
38+
method: GET
39+
endpoint: https://api.example.com/timeout-prone-service
40+
timeout: 5
41+
catch:
42+
errors:
43+
with:
44+
type: https://serverlessworkflow.io/dsl/errors/types/timeout
45+
as: timeoutError
46+
do:
47+
handleTimeout:
48+
set:
49+
value: ${ { "status": "timeout", "error": timeoutError, "message": "Request timed out" } }
50+
51+
- finalizeWorkflow:
52+
set:
53+
value: "Workflow completed with error handling"

0 commit comments

Comments
 (0)