diff --git a/ctk/features/try.feature b/ctk/features/try.feature
index 6ac7800c..1929cdc4 100644
--- a/ctk/features/try.feature
+++ b/ctk/features/try.feature
@@ -84,4 +84,127 @@ Feature: Try Task
petName: Milou
"""
When the workflow is executed
- Then the workflow should fault
\ No newline at end of file
+ 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'
\ No newline at end of file
diff --git a/dsl-reference.md b/dsl-reference.md
index a6a9e9e3..0b376e0d 100644
--- a/dsl-reference.md
+++ b/dsl-reference.md
@@ -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`
[`timeout`](#timeout) | `no` | The configuration of the task's timeout, if any.
*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.
*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
@@ -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
@@ -1124,6 +1130,36 @@ Defines the configuration of a catch clause, which a concept used to catch error
| retry | `string`
[`retryPolicy`](#retry) | `no` | The [`retry policy`](#retry) to use, if any, when catching [`errors`](#error).
*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.
diff --git a/dsl.md b/dsl.md
index 747a123f..9c9b649b 100644
--- a/dsl.md
+++ b/dsl.md
@@ -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
@@ -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.
diff --git a/examples/task-level-catch-basic.yaml b/examples/task-level-catch-basic.yaml
new file mode 100644
index 00000000..f115ef4c
--- /dev/null
+++ b/examples/task-level-catch-basic.yaml
@@ -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
\ No newline at end of file
diff --git a/examples/task-level-catch-http.yaml b/examples/task-level-catch-http.yaml
new file mode 100644
index 00000000..a23e69b2
--- /dev/null
+++ b/examples/task-level-catch-http.yaml
@@ -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"
\ No newline at end of file
diff --git a/examples/task-level-vs-try-catch.yaml b/examples/task-level-vs-try-catch.yaml
new file mode 100644
index 00000000..e119b30d
--- /dev/null
+++ b/examples/task-level-vs-try-catch.yaml
@@ -0,0 +1,90 @@
+document:
+ dsl: '1.0.0'
+ namespace: examples
+ name: task-level-vs-try-catch
+ version: '1.0.0'
+description: |
+ This example demonstrates the difference between task-level catch and try-catch blocks.
+ It shows when to use each approach and how they can be combined effectively.
+
+do:
+ # Example 1: Task-level catch for individual error handling
+ - individualTask:
+ call: http
+ with:
+ method: GET
+ endpoint: https://api.example.com/service1
+ catch:
+ errors:
+ with:
+ type: https://serverlessworkflow.io/dsl/errors/types/communication
+ as: service1Error
+ do:
+ handleService1Error:
+ set:
+ value: ${ { "service": "service1", "error": service1Error } }
+
+ # Example 2: Try-catch block for group error handling
+ - groupedTasks:
+ try:
+ do:
+ - service2Call:
+ call: http
+ with:
+ method: GET
+ endpoint: https://api.example.com/service2
+
+ - service3Call:
+ call: http
+ with:
+ method: POST
+ endpoint: https://api.example.com/service3
+ body: ${ .service2Call }
+
+ catch:
+ errors:
+ with:
+ type: https://serverlessworkflow.io/dsl/errors/types/communication
+ as: groupError
+ do:
+ handleGroupError:
+ set:
+ value: ${ { "group": "service2-3", "error": groupError } }
+
+ # Example 3: Combining both approaches
+ - combinedExample:
+ try:
+ do:
+ - service4Call:
+ call: http
+ with:
+ method: GET
+ endpoint: https://api.example.com/service4
+ catch:
+ errors:
+ with:
+ type: https://serverlessworkflow.io/dsl/errors/types/timeout
+ retry:
+ delay:
+ seconds: 1
+ limit:
+ attempt:
+ count: 2
+
+ - service5Call:
+ call: http
+ with:
+ method: POST
+ endpoint: https://api.example.com/service5
+ catch:
+ errors:
+ with:
+ type: https://serverlessworkflow.io/dsl/errors/types/communication
+ do:
+ handleOverallError:
+ set:
+ value: "Handling any remaining communication errors"
+
+ - finalStatus:
+ set:
+ value: "Workflow completed with different error handling approaches"
\ No newline at end of file
diff --git a/schema/workflow.yaml b/schema/workflow.yaml
index 5473f059..8a747ab7 100644
--- a/schema/workflow.yaml
+++ b/schema/workflow.yaml
@@ -203,6 +203,8 @@ $defs:
title: TaskMetadata
description: Holds additional information about the task.
additionalProperties: true
+ catch:
+ $ref: '#/$defs/taskCatch'
task:
title: Task
description: A discrete unit of work that contributes to achieving the overall objectives defined by the workflow.
@@ -820,42 +822,7 @@ $defs:
title: TryTaskConfiguration
description: The task(s) to perform.
catch:
- type: object
- title: TryTaskCatch
- description: The object used to define the errors to catch.
- unevaluatedProperties: false
- properties:
- errors:
- type: object
- title: CatchErrors
- properties:
- with:
- $ref: '#/$defs/errorFilter'
- description: static error filter
- as:
- type: string
- title: CatchAs
- description: The name of the runtime expression variable to save the error as. Defaults to 'error'.
- when:
- type: string
- title: CatchWhen
- description: A runtime expression used to determine whether to catch the filtered error.
- exceptWhen:
- type: string
- title: CatchExceptWhen
- description: A runtime expression used to determine whether not to catch the filtered error.
- retry:
- oneOf:
- - $ref: '#/$defs/retryPolicy'
- title: RetryPolicyDefinition
- description: The retry policy to use, if any, when catching errors.
- - type: string
- title: RetryPolicyReference
- description: The name of the retry policy to use, if any, when catching errors.
- do:
- $ref: '#/$defs/taskList'
- title: TryTaskCatchDo
- description: The definition of the task(s) to run when catching an error.
+ $ref: '#/$defs/taskCatch'
waitTask:
type: object
$ref: '#/$defs/taskBase'
@@ -1773,4 +1740,41 @@ $defs:
export:
$ref: '#/$defs/export'
title: SubscriptionIteratorExport
- description: An object, if any, used to customize the content of the workflow context.
\ No newline at end of file
+ description: An object, if any, used to customize the content of the workflow context.
+ taskCatch:
+ type: object
+ title: TaskCatch
+ description: The object used to define error handling at the task level.
+ unevaluatedProperties: false
+ properties:
+ errors:
+ type: object
+ title: CatchErrors
+ properties:
+ with:
+ $ref: '#/$defs/errorFilter'
+ description: static error filter
+ as:
+ type: string
+ title: CatchAs
+ description: The name of the runtime expression variable to save the error as. Defaults to 'error'.
+ when:
+ type: string
+ title: CatchWhen
+ description: A runtime expression used to determine whether to catch the filtered error.
+ exceptWhen:
+ type: string
+ title: CatchExceptWhen
+ description: A runtime expression used to determine whether not to catch the filtered error.
+ retry:
+ oneOf:
+ - $ref: '#/$defs/retryPolicy'
+ title: RetryPolicyDefinition
+ description: The retry policy to use, if any, when catching errors.
+ - type: string
+ title: RetryPolicyReference
+ description: The name of the retry policy to use, if any, when catching errors.
+ do:
+ $ref: '#/$defs/taskList'
+ title: TaskCatchDo
+ description: The definition of the task(s) to run when catching an error.
\ No newline at end of file