From bf30a864aecbea353da5943c15a8c7eae7163eaf Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Mon, 8 Aug 2022 15:54:19 -0300 Subject: [PATCH] Fixes #420 - Add REST invocation function definition based on OpenAPI Path Item Signed-off-by: Ricardo Zanini --- roadmap/README.md | 2 +- schema/functions.json | 35 ++++++-- specification.md | 191 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 211 insertions(+), 17 deletions(-) diff --git a/roadmap/README.md b/roadmap/README.md index ba29fbd7..33ba1bbf 100644 --- a/roadmap/README.md +++ b/roadmap/README.md @@ -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 | | diff --git a/schema/functions.json b/schema/functions.json index a29bd2d7..4863c6fc 100644 --- a/schema/functions.json +++ b/schema/functions.json @@ -35,15 +35,15 @@ "minLength": 1 }, "operation": { - "type": "string", - "description": "If type is `rest`, #. If type is `asyncapi`, #. If type is `rpc`, ##. If type is `graphql`, ##. If type is `odata`, #. 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", @@ -51,7 +51,7 @@ "expression", "custom" ], - "default": "rest" + "default": "openapi" }, "authRef": { "oneOf": [ @@ -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" } @@ -90,6 +90,25 @@ "name", "operation" ] + }, + "operation": { + "oneOf": [ + { + "type": "string", + "description": "If type is `openapi`, #. If type is `asyncapi`, #. If type is `rpc`, ##. If type is `graphql`, ##. If type is `odata`, #. 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" + } + } + } + ] } } } \ No newline at end of file diff --git a/specification.md b/specification.md index f14402e8..d48648ec 100644 --- a/specification.md +++ b/specification.md @@ -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) @@ -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) @@ -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 @@ -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). @@ -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`, #. If type is `asyncapi`, #. If type is `rpc`, ##. If type is `graphql`, ##. If type is `odata`, #. 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` | # | +| `rest` | [OpenAPI Paths Object](https://spec.openapis.org/oas/v3.1.0#paths-object) definition | +| `asyncapi` | # | +| `rpc` | ## | +| `graphql` | ## | +| `odata` | # | +| `expression` | defines the workflow expression | +| `custom` | see [Defining custom function types](#defining-custom-function-types) +
Click to view example definition

@@ -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 '#'.