Utilities

This section contains the following topics:

System-defined utilities

System-defined utilities are functions that expose activities to be used by a workflow creator. Invoking them as actions inside a workflow helps fulfill basic tasks without the need to create custom adapters. They come pre-packaged and are ready-to-use in any workflow definition.

Invoke a system utility in a workflow

To use a system utility function in a workflow execution, you must first define it under the functions key in the workflow. Let’s take the NoOp function as an example.

  "functions": [
    {
      "name": "noop",
      "metadata": {
        "worker": "default"
      },
      "operation": "system.function.@latest.common.NoOp"
    }
  ],
  • name: A user-defined name for the utility function referred to inside the workflow definition.

  • operation: This is where you provide the utility function name (name of activity as with an adapter activity).

  • worker: Defining a worker is mandatory for all utility functions. Use the default worker for this.

Make sure to define the worker key with the value default for each utility function.

 "states": [
    {
      "name": "start",
      "type": "callback",
      "action": {
        "name": "no operation",
        "functionRef": {
          "refName": "noop",
          "arguments": {}
        }
      },
      "eventRef": "cwm.forms.Check_applicant_age",
      "metadata": {
        "formData": "${ {user : .user} }",
        "taskName": "Provide applicant age"
      },
      "timeouts": {
        "eventTimeout": "PT15S",
        "stateExecTimeout": "PT15M",
        "actionExecTimeout": "PT15S"
      },
      "transition": "evaluate"
    },
    ...
 ]

Note that the callback state requires the action parameter to be stated. The name and functionRef keys need to have values provided even if no action is expected to happen during this state. Therefore, a mock action needs to be provided. For this, you can use a CWM utility function, like noOp, or any activity of your custom adapter, but keep in mind that the action needs to complete successfully for the workflow to pass to another step.

See the descriptions of utilities below to learn more about them.

FailJob

system.function.@latest.common.FailJob

The FailJob function allows a workflow creator to explicitly fail a workflow with a code and message when certain conditions are met, which currently is not provided for in the Serverless workflow specification.

GetResourceType

system.function.@latest.resource.GetResourceType

GetResourceType returns the Resource type for a given resource ID. You provide the resource ID using the resourceId parameter inside the input key under arguments. You can provide it explicitly or use a variable that you will pass as the input data for the workflow.

Example:

      "name": "begin",
      "type": "operation",
      "action": {
        "name": "getResType",
        "functionRef": {
          "refName": "GetResourceType",
          "arguments": {
            "input": {
                "resourceId": "${ .resID }"
            }
          }
        }
      },

NoOp

system.function.@latest.common.NoOp

NoOp is used to perform a mock action during workflow execution. A great example of a use case for NoOp is the Callback state, which mandates that an action be invoked before the callback state waits for an event. It is perfectly valid for a Callback state to execute a mock action and just wait for an event which is capturing data from the user. The NoOp function will help bypass the action and just wait for the defined event.

WaitUntil

system.function.@latest.common.WaitUntil

WaitUntil introduces a pause in workflow execution based on a user-provided timestamp. The timestamp is the time until which the workflow should wait provided in the RFC 3339 standard format using the timestamp parameter inside arguments.

Example:

      "name": "begin",
      "type": "operation",
      "action": {
        "name": "WaitUntil",
        "functionRef": {
          "refName": "WaitUntil",
          "arguments": {
            "timestamp": "2024-09-19T16:32:37+02:00"
          }
        }
      },

Expression functions

Expression functions are reusable logic blocks defined in a workflow. They evaluate specific conditions or expressions using workflow or state data. You can reference them in different workflow states by their name.

Defining an expression function

Inside a workflow definition, functions are defined with a type: expression parameter and an operation that specifies the logic in the form of a jq expression.

For example:

"functions": [
  {
    "name": "is-adult",
    "operation": ".applicant | .age >= 18",
    "type": "expression"
  },
]

Workflow states can call these functions to make decisions. For instance, a switch state can use "is-adult" to decide if the application should be approved or rejected based on age. Expression functions can also be used in state actions to perform calculations. For instance, an action increments a counter by 1 using the "increment-count-function". The workflow starts with a count of 0, and after the function runs, the count becomes 1.

Example workflow

{
 "id": "fillglassofwater",
 "name": "Fill glass of water workflow",
 "version": "1.0",
 "specVersion": "0.8",
 "start": "Check if full",
 "functions": [
  {
   "name": "Increment Current Count Function",
   "type": "expression",
   "operation": "${.counts.current += 1 | .counts.current}"
  }
 ],
 "states": [
  {
   "name": "Check if full",
   "type": "switch",
   "dataConditions": [
    {
     "name": "Need to fill more",
     "condition": "${ .counts.current < .counts.max }",
     "transition": "Add Water"
    },
    {
     "name": "Glass full",
     "condition": "${ .counts.current >= .counts.max }",
     "end": true
    }
   ],
   "defaultCondition": {
    "end": true
   }
  },
  {
   "name": "Add Water",
   "type": "operation",
   "actions": [
    {
     "functionRef": "Increment Current Count Function",
     "actionDataFilter": {
      "toStateData": "${ .counts.current }"
     }
    }
   ],
   "transition": "Check if full"
  }
 ]
}

The expression function in this workflow increments the current count of water added to the glass and checks it against the maximum count, ensuring the process of filling is controlled and stops once the glass is full.

Input schema validation

The Input schema forms a contract between workflows and the entities that interact with them. If data that is provided to a workflow does not conform to the input schema, this could potentially result in catastrophic errors during execution. Validating the workflow upfront means the operator can be informed beforehand and get immediate feedback and ability to rectify any potential issues due to invalid data input.

The Serverless workflow specification 0.9 allows a workflow definition to specify the dataInputSchema parameter. The schema uses JSON Schema specification to define what inputs are required for the workflow to execute. CWM 1.2 supports the input schema validation for a workflow definition to ensure that when a new job is created, input data is validated against the dataInputSchema. If input data for a job is not valid, an error is returned.

Here is how you define an input schema inside a workflow definition.

Define input schema for workflow validation

To use an input schema to validate a workflow, simply include a valid JSON schema in a workflow using the dataInputSchema parameter. For example:

{
  "dataInputSchema": {
    "schema": {
      "title": "MyJSONSchema",
      "properties": {
        "firstName": {
          "type": "string"
        },
        "lastName": {
          "type": "string"
        }
      }
    },
    "failOnValidationErrors": true
  }
}

The dataInputSchema itself is validated when a workflow is added or modified. This applies to both creating and importing workflow definitions. On the other hand, the input data for a job is validated before the job is run. This applies to both immediate and scheduled job execution.


Tip


You can turn off the validation while leaving the input schema in the workflow definition by setting the failOnValidationErrors parameter to false.


Required keys

In the schema specification, all the keys and values are optional by default. To make them mandatory, you need to specify them under the required key. For exampl3e:

"dataInputSchema": {
    "schema": {
      "title": "MyJSONSchema",
      "required": [
        "deviceName"
      ],
      "properties": {
        "deviceName": {
          "type": "string"
        },
        "nsoResource": {
          "type": "string"
        }
      }
    },
    "failOnValidationErrors": true
}

In this example, the deviceName key is now required. If the input data does not contain it, the job won't start and a validation error will be returned.