Skip to content

Fixes #420 - Add REST invocation function definition based on OpenAPI Path Object #652

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

Merged
merged 2 commits into from
Oct 19, 2023
Merged
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
2 changes: 1 addition & 1 deletion roadmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ _Status description:_
| ✔️| 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 |
| ✔️| Update `ForEach` state iteration parameter example. This parameter is an expression variable, not a JSON property |
| ✔️| 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 @@ -36,23 +36,23 @@
"pattern": "^[a-z0-9](-?[a-z0-9])*$"
},
"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 @@ -65,14 +65,14 @@
{
"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,
"pattern": "^[a-z0-9](-?[a-z0-9])*$"
},
"invocation":{
"invocation": {
"type": "string",
"description": "References an auth definition to be used to invoke the operation"
}
Expand All @@ -93,6 +93,25 @@
"name",
"operation"
]
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think this is right. we should find a way to restrict object type on the value of type property please.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also description if object type should add something like "added only for rest type"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. The AuthRef object can be applied to any remote invocation such as gRPC, GraphQL, openapi, rest, odata, etc.

If we would make this change, I'd say to do it in another PR. There's no change here apart from the formatting and the default to openapi type.

Copy link
Member

@cdavernas cdavernas May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved? I personally agree with @ricardozanini on this one!

"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",
Copy link
Contributor

@tsurdilo tsurdilo Sep 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think we use schema 7.x comments anywhere do we? maybe can just put in description and try to be version independent

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have been using schema 7.x since the beginning, and I don't think it will break anything. $comment is pretty useful since SDKs/Tooling can use it to parse this data against the OpenAPI spec. So we don't need to replicate their structure here.

Additionally, schema 7.x is from March 2018.

Honestly, I don't see why we should not use $comment based on this tradeoff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsurdilo Any further toughts? If not, please resolve ;)

"patternProperties": {
"^/": {
"type": "object"
}
}
}
]
}
}
}
193 changes: 187 additions & 6 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 @@ -994,8 +995,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 @@ -1005,7 +1007,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 @@ -1058,10 +1060,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](https://spec.openapis.org/oas/v3.1.0#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",
"type": "rest",
"operation": {
"/users": {
"post": {
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"required": ["name", "email"]
}
}
}
}
}
}
}
}
]
}
```

Note that the `requestBody` [`content` attribute](https://spec.openapis.org/oas/v3.1.0#fixed-fields-10) 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. Given the workflow input data:

```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) since its redundat to function [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 @@ -3197,12 +3357,31 @@ Note that `transition` and `end` properties are mutually exclusive, meaning that

| Parameter | Description | Type | Required |
| --- | --- | --- | --- |
<<<<<<< HEAD
| name | Unique function name. Must follow the [Serverless Workflow Naming Convention](#naming-convention) | 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 |
=======
| name | Unique function name. Must follow the [Serverless Workflow Naming Convention](#naming-convention) | string | yes |
| 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 |
>>>>>>> 2351b12 (Fixes #420 - Add REST invocation function definition based on OpenAPI Path Item)
| 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 @@ -3237,12 +3416,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` enum 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