Skip to content

Commit

Permalink
Fixes #420 - Add REST invocation function definition based on OpenAPI…
Browse files Browse the repository at this point in the history
… Path Item

Signed-off-by: Ricardo Zanini <zanini@redhat.com>
  • Loading branch information
ricardozanini committed Aug 8, 2022
1 parent 7023383 commit bf30a86
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 17 deletions.
2 changes: 1 addition & 1 deletion roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ _Status description:_
| ✔️| Apply fixes to auth spec schema [workflow schema](https://github.com/serverlessworkflow/specification/tree/main/schema) |
| ✔️| Update the `dataInputSchema` top-level property by supporting the assignment of a JSON schema object [workflow schema](https://github.com/serverlessworkflow/specification/tree/main/specification.md#workflow-definition-structure) |
| ✔️| Add the new `WORKFLOW` reserved keyword to workflow expressions |
| ✔️| Add the new `rest` function type [spec doc](https://github.com/serverlessworkflow/specification/tree/main/specification.md#using-functions-for-restful-service-invocations) |
| ✏️️| Add inline state defs in branches | |
| ✏️️| Update rest function definition | |
| ✏️️| Add "completedBy" functionality | |
| ✏️️| Define workflow context | |
| ✏️️| Start work on TCK | |
Expand Down
35 changes: 27 additions & 8 deletions schema/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,23 @@
"minLength": 1
},
"operation": {
"type": "string",
"description": "If type is `rest`, <path_to_openapi_definition>#<operation_id>. If type is `asyncapi`, <path_to_asyncapi_definition>#<operation_id>. If type is `rpc`, <path_to_grpc_proto_file>#<service_name>#<service_method>. If type is `graphql`, <url_to_graphql_endpoint>#<literal \\\"mutation\\\" or \\\"query\\\">#<query_or_mutation_name>. If type is `odata`, <URI_to_odata_service>#<Entity_Set_Name>. If type is `expression`, defines the workflow expression.",
"minLength": 1
"type": "object",
"$ref": "#/definitions/operation"
},
"type": {
"type": "string",
"description": "Defines the function type. Is either `rest`, `asyncapi, `rpc`, `graphql`, `odata`, `expression`, or `custom`. Default is `rest`",
"description": "Defines the function type. Is either `rest`, `openapi`,`asyncapi, `rpc`, `graphql`, `odata`, `expression`, or `custom`. Default is `openapi`.",
"enum": [
"rest",
"openapi",
"asyncapi",
"rpc",
"graphql",
"odata",
"expression",
"custom"
],
"default": "rest"
"default": "openapi"
},
"authRef": {
"oneOf": [
Expand All @@ -63,13 +63,13 @@
{
"type": "object",
"description": "Configures both the auth definition used to retrieve the operation's resource and the auth definition used to invoke said operation",
"properties":{
"resource":{
"properties": {
"resource": {
"type": "string",
"description": "References an auth definition to be used to access the resource defined in the operation parameter",
"minLength": 1
},
"invocation":{
"invocation": {
"type": "string",
"description": "References an auth definition to be used to invoke the operation"
}
Expand All @@ -90,6 +90,25 @@
"name",
"operation"
]
},
"operation": {
"oneOf": [
{
"type": "string",
"description": "If type is `openapi`, <path_to_openapi_definition>#<operation_id>. If type is `asyncapi`, <path_to_asyncapi_definition>#<operation_id>. If type is `rpc`, <path_to_grpc_proto_file>#<service_name>#<service_method>. If type is `graphql`, <url_to_graphql_endpoint>#<literal \\\"mutation\\\" or \\\"query\\\">#<query_or_mutation_name>. If type is `odata`, <URI_to_odata_service>#<Entity_Set_Name>. If type is `expression`, defines the workflow expression.",
"minLength": 1
},
{
"type": "object",
"description": "OpenAPI Path Object definition",
"$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object",
"patternProperties": {
"^/": {
"type": "object"
}
}
}
]
}
}
}
191 changes: 183 additions & 8 deletions specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
+ [Using multiple data filters](#using-multiple-data-filters)
+ [Data Merging](#data-merging)
* [Workflow Functions](#workflow-functions)
+ [Using Functions for OpenAPI Service Invocations](#using-functions-for-openapi-service-invocations)
+ [Using Functions for RESTful Service Invocations](#using-functions-for-restful-service-invocations)
+ [Using Functions for Async API Service Invocations](#using-functions-for-async-api-service-invocations)
+ [Using Functions for RPC Service Invocations](#using-functions-for-rpc-service-invocations)
Expand Down Expand Up @@ -991,8 +992,9 @@ They can be referenced by their domain-specific names inside workflow [states](#

Reference the following sections to learn more about workflow functions:

* [Using functions for RESTful service invocations](#Using-Functions-for-RESTful-Service-Invocations)
* [Using Functions for Async API Service Invocations](#Using-Functions-for-Async-API-Service-Invocations)
* [Using functions for OpenAPI Service invocations](#using-functions-for-openapi-service-invocations)
+ [Using functions for RESTful Service Invocations](#using-functions-for-rest-service-invocations)
* [Using functions for Async API Service Invocations](#Using-Functions-for-Async-API-Service-Invocations)
* [Using functions for gRPC service invocation](#Using-Functions-For-RPC-Service-Invocations)
* [Using functions for GraphQL service invocation](#Using-Functions-For-GraphQL-Service-Invocations)
* [Using Functions for OData Service Invocations](#Using-Functions-for-OData-Service-Invocations)
Expand All @@ -1002,7 +1004,7 @@ Reference the following sections to learn more about workflow functions:
We can define if functions are invoked sync or async. Reference
the [functionRef](#FunctionRef-Definition) to learn more on how to do this.

#### Using Functions for RESTful Service Invocations
#### Using Functions for OpenAPI Service Invocations

[Functions](#Function-Definition) can be used to describe services and their operations that need to be invoked during
workflow execution. They can be referenced by states [action definitions](#Action-Definition) to clearly
Expand Down Expand Up @@ -1055,10 +1057,168 @@ For example:
}
```

Note that the referenced function definition type in this case must be `rest` (default type).
Note that the referenced function definition type in this case must be `openapi` (default type).

For more information about functions, reference the [Functions definitions](#Function-Definition) section.

#### Using functions for RESTful Service Invocations

The specification also supports describing REST invocations in the [functions definition](#Function-Definition) using [OpenAPI Paths Object](https://spec.openapis.org/oas/v3.1.0#paths-object).

Here is an example function definition for REST requests with method `GET` and request target corresponding with [URI Template](https://www.rfc-editor.org/rfc/rfc6570.html) `/users/{id}`:

```json
{
"functions":[
{
"name":"queryUserById",
"operation": {
"/users": {
"get": {
"parameters": [{
"name": "id",
"in": "path",
"required": true
}]
}
}
},
"type":"rest"
}
]
}
```

Note that the [Function Definition](#Function-Definition)'s `operation` property must follow the OpenAPI Paths Object specification definition.

The function can be referenced during workflow execution when the invocation of the REST service is desired. For example:

```json
{
"states":[
{
"name":"QueryUserInfo",
"type":"operation",
"actions":[
{
"functionRef":"queryUserById",
"arguments":{
"id":"${ .user.id }"
}
}
],
"end":true
}
]
}
```

Example of the `POST` request sending the state data as part of the body:

```json
{
"functions":[
{
"name": "createUser",
"operation": {
"/users": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"required": ["name", "email"]
}
}
}
}
}
}
},
"type": "rest"
}
]
}
```

Note that the `requestBody` content schema is described inline rather than a reference to an external document.

You can reference the `createUser` function and filter the input data to invoke it, as shown in the example below:

```json
{
"order":{
"id":"1234N",
"products":[
{
"name":"Product 1"
}
]
},
"user":{
"name":"John Doe",
"email":"john@doe.com"
}
}
```

Function invocation example:

```json
{
"states":[
{
"name":"CreateNewUser",
"type":"operation",
"actions":[
{
"functionRef":"createUser",
"actionDataFilter":{
"fromStateData":"${ .user }",
"toStateData":"${ .user.id }"
}
}
],
"end":true
}
]
}
```

In this case, only the contents of the `user` attribute will be passed to the function. The user ID returned by the REST request body will then be added to the state data:

```json
{
"order":{
"id":"1234N",
"products":[
{
"name":"Product 1"
}
]
},
"user":{
"id":"5678U",
"name":"John Doe",
"email":"john@doe.com"
}
}
```

The specification does not support the [Security Requirement Object](https://spec.openapis.org/oas/v3.1.0#security-requirement-object). To define authentication for the REST operation, use the [Auth Definition](#Auth-Definition). If provided, this field is ignored.

#### Using Functions for Async API Service Invocations

[Functions](#Function-Definition) can be used to invoke PUBLISH and SUBSCRIBE operations on a message broker documented by the [Async API Specification](https://www.asyncapi.com/docs/specifications/v2.1.0).
Expand Down Expand Up @@ -3189,11 +3349,24 @@ Note that `transition` and `end` properties are mutually exclusive, meaning that
| Parameter | Description | Type | Required |
| --- | --- | --- | --- |
| name | Unique function name | string | yes |
| operation | If type is `rest`, <path_to_openapi_definition>#<operation_id>. If type is `asyncapi`, <path_to_asyncapi_definition>#<operation_id>. If type is `rpc`, <path_to_grpc_proto_file>#<service_name>#<service_method>. If type is `graphql`, <url_to_graphql_endpoint>#<literal \"mutation\" or \"query\">#<query_or_mutation_name>. If type is `odata`, <URI_to_odata_service>#<Entity_Set_Name>. If type is `expression`, defines the workflow expression. | string | yes |
| type | Defines the function type. Can be either `rest`, `asyncapi`, `rpc`, `graphql`, `odata`, `expression`, or [`custom`](#defining-custom-function-types). Default is `rest` | enum | no |
| operation | See the table "Function Operation description by type" below. | string or object | yes |
| type | Defines the function type. Can be either `rest`, `openapi`, `asyncapi`, `rpc`, `graphql`, `odata`, `expression`, or [`custom`](#defining-custom-function-types). Default is `openapi` | enum | no |
| authRef | References an [auth definition](#Auth-Definition) name to be used to access to resource defined in the operation parameter | string | no |
| [metadata](#Workflow-Metadata) | Metadata information. Can be used to define custom function information | object | no |

Function Operation description by type:

| Type | Operation Description |
| ---- | --------- |
| `openapi` | <path_to_openapi_definition>#<operation_id> |
| `rest` | [OpenAPI Paths Object](https://spec.openapis.org/oas/v3.1.0#paths-object) definition |
| `asyncapi` | <path_to_asyncapi_definition>#<operation_id> |
| `rpc` | <path_to_grpc_proto_file>#<service_name>#<service_method> |
| `graphql` | <url_to_graphql_endpoint>#<literal \"mutation\" or \"query\">#<query_or_mutation_name> |
| `odata` | <URI_to_odata_service>#<Entity_Set_Name> |
| `expression` | defines the workflow expression |
| `custom` | see [Defining custom function types](#defining-custom-function-types)

<details><summary><strong>Click to view example definition</strong></summary>
<p>

Expand Down Expand Up @@ -3228,12 +3401,14 @@ operation: https://hellworldservice.api.com/api.json#helloWorld

The `name` property defines an unique name of the function definition.

The `type` property defines the function type. Its value can be either `rest` or `expression`. Default value is `rest`.
The `type` property defines the function type. Its value can be either `rest`, `openapi` or `expression`. Default value is `openapi`.

Depending on the function `type`, the `operation` property can be:

* If `type` is `rest`, a combination of the function/service OpenAPI definition document URI and the particular service operation that needs to be invoked, separated by a '#'.
* If `type` is `openapi`, a combination of the function/service OpenAPI definition document URI and the particular service operation that needs to be invoked, separated by a '#'.
For example `https://petstore.swagger.io/v2/swagger.json#getPetById`.
* If `type` is `rest`, an object definition of the [OpenAPI Paths Object](https://spec.openapis.org/oas/v3.1.0#paths-object).
For example, see [Using Functions for RESTful Service Invocations](#using-functions-for-rest-service-invocations).
* If `type` is `asyncapi`, a combination of the AsyncApi definition document URI and the particular service operation that needs to be invoked, separated by a '#'.
For example `file://streetlightsapi.yaml#onLightMeasured`.
* If `type` is `rpc`, a combination of the gRPC proto document URI and the particular service name and service method name that needs to be invoked, separated by a '#'.
Expand Down

0 comments on commit bf30a86

Please sign in to comment.