Skip to content

feat(1091): allow catch on any task #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 124 additions & 1 deletion ctk/features/try.feature
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,127 @@ Feature: Try Task
petName: Milou
"""
When the workflow is executed
Then the workflow should fault
Then the workflow should fault

# Tests task-level catch functionality
# Tests error handling without try block
# Tests custom error variable name
# Tests error instance path
Scenario: Task Level Catch Handle Error
Given a workflow with definition:
"""yaml
document:
dsl: '1.0.0'
namespace: default
name: task-level-catch
version: '1.0.0'
do:
- getPet:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/communication
status: 404
as: taskError
do:
- handleError:
set:
error: ${ $taskError }
"""
And given the workflow input is:
"""yaml
petName: Milou
"""
When the workflow is executed
Then the workflow should complete
And the workflow output should have properties 'error', 'error.type', 'error.status', 'error.title'
And the workflow output should have a 'error.instance' property with value:
"""yaml
/do/0/getPet
"""

# Tests task-level catch with retry
# Tests retry policy configuration
# Tests error handling
Scenario: Task Level Catch With Retry
Given a workflow with definition:
"""yaml
document:
dsl: '1.0.0'
namespace: default
name: task-level-catch-retry
version: '1.0.0'
do:
- getPet:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/communication
status: 503
retry:
delay:
seconds: 2
backoff:
exponential: {}
limit:
attempt:
count: 3
do:
- handleError:
set:
status: "service_unavailable"
"""
And given the workflow input is:
"""yaml
petName: Milou
"""
When the workflow is executed
Then the workflow should complete
And the workflow output should have a 'status' property with value 'service_unavailable'

# Tests task-level catch with conditional handling
# Tests when condition in catch
# Tests error variable access
Scenario: Task Level Catch With Condition
Given a workflow with definition:
"""yaml
document:
dsl: '1.0.0'
namespace: default
name: task-level-catch-condition
version: '1.0.0'
do:
- getPet:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName}
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/communication
when: $error.status == 404
as: notFoundError
do:
- handleNotFound:
set:
message: "Pet not found"
"""
And given the workflow input is:
"""yaml
petName: Milou
"""
When the workflow is executed
Then the workflow should complete
And the workflow output should have a 'message' property with value 'Pet not found'
38 changes: 37 additions & 1 deletion dsl-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ The Serverless Workflow DSL defines a list of [tasks](#task) that **must be** su
| export | [`export`](#export) | `no` | An object used to customize the content of the workflow context. |
| 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).* |
| then | [`flowDirective`](#flow-directive) | `no` | The flow directive to execute next.<br>*If not set, defaults to `continue`.* |
| catch | [`catch`](#catch) | `no` | Used to handle errors and define retry policies for the current task. See [Catch](#catch) for detailed documentation. |
| metadata | `map` | `no` | Additional information about the task. |

#### Call
Expand Down Expand Up @@ -1111,7 +1112,12 @@ do:

##### Catch

Defines the configuration of a catch clause, which a concept used to catch errors.
Defines the configuration of a catch clause, which is a concept used to catch errors and to define what should happen when they occur.
Catch nodes can be used in two ways:
1. With any [`task`](#task) to handle errors on that single task
2. With the [`try`](#try) task to handle errors on a group of tasks

This flexibility allows for both fine-grained error handling at the task level and broader error handling for groups of tasks.

###### Properties

Expand All @@ -1124,6 +1130,36 @@ Defines the configuration of a catch clause, which a concept used to catch error
| 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).* |
| do | [`map[string, task]`](#task) | `no` | The definition of the task(s) to run when catching an error. |

##### Examples

```yaml
document:
dsl: '1.0.0'
namespace: test
name: catch-example
version: '0.1.0'
do:
- invalidHttpCall:
call: http
with:
method: get
endpoint: https://
catch:
errors:
with:
type: https://serverlessworkflow.io.io/dsl/errors/types/communication
status: 503
as: error
retry:
delay:
seconds: 3
backoff:
exponential: {}
limit:
attempt:
count: 5
```

#### Wait

Allows workflows to pause or delay their execution for a specified period of time.
Expand Down
98 changes: 97 additions & 1 deletion dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ See the [DSL reference](dsl-reference.md#error) for more details about errors.

#### Retries

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.
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.

*Retrying 5 times when an error with 503 is caught:*
```yaml
Expand All @@ -507,6 +507,102 @@ catch:
count: 5
```

#### Task-Level Error Handling

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.

Task-level error handling provides several benefits:
1. More concise and readable code by avoiding nested try blocks
2. Direct association of error handling with the task that might fail
3. Reusability of error handling logic when the task is used as a function

*Example of task-level error handling:*
```yaml
do:
- getPetById:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/{id}
catch:
errors:
with:
status: 404
do:
- createDefaultPet:
call: http
with:
method: post
endpoint:
uri: https://petstore.swagger.io/v2/pet
body:
id: ${ .id }
name: "Default Pet"
status: "available"
```

*Example comparing try-catch with task-level catch:*
```yaml
# Using try-catch
do:
- handlePet:
try:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/{id}
catch:
errors:
with:
status: 404
do:
- createDefaultPet:
call: http
with:
method: post
endpoint:
uri: https://petstore.swagger.io/v2/pet
body:
id: ${ .id }
name: "Default Pet"
status: "available"

# Using task-level catch (more concise)
do:
- getPetById:
call: http
with:
method: get
endpoint:
uri: https://petstore.swagger.io/v2/pet/{id}
catch:
errors:
with:
status: 404
do:
- createDefaultPet:
call: http
with:
method: post
endpoint:
uri: https://petstore.swagger.io/v2/pet
body:
id: ${ .id }
name: "Default Pet"
status: "available"
```

Task-level error handling supports all the same features as try-catch:
- Error filtering with `with`
- Error variable naming with `as`
- Conditional catching with `when` and `exceptWhen`
- Retry policies
- Task list execution with `do`

See the [DSL reference](dsl-reference.md#task-catch) for more details about task-level error handling.

### Timeouts

Workflows and tasks alike can be configured to timeout after a defined amount of time.
Expand Down
32 changes: 32 additions & 0 deletions examples/task-level-catch-basic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
document:
dsl: '1.0.0'
namespace: examples
name: task-level-catch-basic
version: '1.0.0'
description: |
This example demonstrates basic task-level error handling using catch.
It shows how to handle errors directly on individual tasks without using a try block.

do:
- failingTask:
call: custom
with:
function: simulateFailure
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/custom
as: taskError
do:
handleError:
set:
value: ${ { "error": taskError, "message": "Handled error at task level" } }

- successTask:
set:
value: "Task completed successfully after error handling"

functions:
- name: simulateFailure
type: custom
operation: fake.operation
53 changes: 53 additions & 0 deletions examples/task-level-catch-http.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
document:
dsl: '1.0.0'
namespace: examples
name: task-level-catch-http
version: '1.0.0'
description: |
This example demonstrates task-level error handling with HTTP calls.
It shows how to handle common HTTP errors like timeouts and server errors,
including retry logic for transient failures.

do:
- callExternalService:
call: http
with:
method: GET
endpoint: https://api.example.com/potentially-unstable-service
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/communication
status: 503
retry:
delay:
seconds: 2
backoff:
exponential: {}
limit:
attempt:
count: 3
do:
handleServiceUnavailable:
set:
value: ${ { "status": "service_unavailable", "message": "External service temporarily unavailable" } }

- callAnotherEndpoint:
call: http
with:
method: GET
endpoint: https://api.example.com/timeout-prone-service
timeout: 5
catch:
errors:
with:
type: https://serverlessworkflow.io/dsl/errors/types/timeout
as: timeoutError
do:
handleTimeout:
set:
value: ${ { "status": "timeout", "error": timeoutError, "message": "Request timed out" } }

- finalizeWorkflow:
set:
value: "Workflow completed with error handling"
Loading
Loading