Fulfillments
A fulfillment is used to retrieve, send or process data in APICHAP. Everytime an operation is executed it will run the list of fulfillments specified in your operation. All the data retrieved within a fulfillment will be temporarily stored within the current operation context to use in the upcoming fulfillments and the response.
Typical examples for a fulfillment you might create:
- A READ fulfillment to SELECT data from a database.
- A UPDATE fulfillment to create and write into a file on an FTP-SERVER.
- A READ fulfillment to download a CSV file from an URL.
- A LOOP fulfillment to loop over an incoming JSON array requestbody.
Depending on the type a fulfillment has a different purpose.
The following types are available:
- READ: Retrieve data from a datasource.
- UPDATE: Update data in your datasource.
- LOOP: Run a list of fulfillments multiple times in a loop.
- IF: Create an if - else branch and define a list of fulfilments to be run in either the if or else branch.
- GENERIC: A generic fulfillment can be used to do many things, not related to READ or UPDATE data in a datasource. Usually used to prepare attributes or handle data checks.
- REFERENCE: A way to execute another operation from the current one.
The types READ and WRITE are mostly very similar and will only make a difference when the instruction has an explicit differently process to read or write data. For example the case when calling an SQL query.
Fulfillments contain the following attributes:
Field | Used in Type | Description | Mandatory |
---|---|---|---|
name | All | Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. | x |
type | All | Define the type of your fulfillment. READ, UPDATE or LOOP | x |
if | All | Specifies an if condition. The fulfillment should only be processed under certain conditions. | |
attributes | All | Define a list of attributes usable in the whole operation context. | |
exceptions | All | Use exceptions to immediately return a HTTP response to the user. | |
sleep | All | Time in milliseconds the thread will sleep before executing the fulfillment. | |
datasource | READ, UPDATE | The datasource this fulfillment should use to retrieve data from. Use the datasource name from the datasources section. | x |
instructions | READ, UPDATE | Define your instructions. An instruction is the actual command on how to retrieve data from your datasource. Like a SQL query, Http-Call, ... | x |
loop | LOOP | Define a JSONArray to loop through and process each entry with a list of sub fulfillments. | |
fulfillments | LOOP | In combination with loop you are able to define a list of fulfillments to run in a loop | |
loopbreak | LOOP | A list of conditions. If they match, the current loop is aborted and apichap will continue with the next fulfillment. | |
index | LOOP | If you run a fulfillment multiple times in a loop, you are able to index each iteration by a value. | |
then | If | Only in combindation with fulfillment type if . Define a list fulfillments executed when the if condition matches to true. |
|
else | If | Only in combindation with fulfillment type if . Define a list fulfillments executed when the if condition matches to false. |
|
operationName | REFERENCE | Only in combindation with fulfillment type reference . Define the operation you would like to trigger. |
|
parameters | REFERENCE | Only in combindation with fulfillment type reference . Define parameters for the operation trigger. |
Fulfillment Name
Rules for naming your fulfillments:
- Fulfillment names are no longer mandatory, however recommended for easier logging.
- Fulfillment names must not contain dots
- Spaces are not recommended
IF
Whenever you want to process a fulfillment only on a certain condition. For example, if
the input Header-Parameter
'Content-Language' is available. This can be achieved with the if
condition.
# Only run fulfillment if Content-Language header is not empty or null
if: PARAM(Content-Language) != NULL()
# Only run a fulfillment if another fulfillment called 'ff1' (already executed) has at least one entry
if: SIZE(VALUE(ff1, $)) > INTEGER(0)
Exceptions
In order to handle error cases you are able to specify exception for each fulfillment. Whenever an exception is triggered the processing of the fulfillments is aborted and the exception´s response is returned directly. It´s possible to define multiple exceptions.
Field | Description | Mandatory |
---|---|---|
if | Define an if condition under which you want to trigger this exception | |
statuscode | The http status code you want to return for the error case. e.g.: 404 | x |
message | The response message you want to return | x |
A classical case is to return HTTP-code 404 when an item is not available in the database. Your exception for this case could look like this:
# we have a fulfillment getting a news entry from a database and want to return 404 if the news id does not exist.
name: ffnewsdetail
type: read
datasource: db
instructions:
- sql:
query: Select * from news WHERE id = ?
parameters:
- param: PARAM(id)
exceptions:
- if: SIZE(VALUE(ffnewsdetail, $)) <= INTEGER(0)
statuscode: 404
message: "Item is not available"
Instructions
Instructions are the process to communicate with your datasource and call for informations. instructions
are the actual place to specify
SQL queries, HTTP-requests, ...
A instruction is only allowed for READ & WRITE fulfillments
Therefore depending on the type of datasource multiple types of configuration options are available:
Instructions IF
No matter what kind of datasource you are using you are always able to set a list of instructions with an if condition. This way it´s possible to send different queries depending on certain conditions.
Field | Description | Mandatory |
---|---|---|
if | Define an if condition under which you want to use this query | |
query / request / file | Depending on your datasource you either have a query, request, ... | x |
Database
An instruction for a database type is fairly easy as you simply provide the SQL query.
To keep your application secure, use prepared statements with ? and specifying your values in the parameters section.
instructions:
- sql:
query: SELECT id FROM news WHERE lang = ?
parameters:
- param: PARAM(Content-Language)
Optional Filters
Often you want to build a SQL query depending on optional filter parameters retrieved. That is possible by using the if
in the parameters and preparing attributes. This could be an example on how to build such a SQL with optional filters:
- name: prepare_filter
summary: Prepare the sql statement for optional filters.
type: generic
attributes:
- if: PARAM(startdate) != NULL()
startdateSQL: TEXT(AND e.startdate >= ?)
- if: PARAM(enddate) != NULL()
enddateSQL: TEXT(AND e.enddate <= ?)
- name: get_filtered_data_events
summary: Get a list of future events including an attendees count based on filters.
type: read
datasource: sample-events
instructions:
- sql:
query: SELECT * FROM events WHERE 1 = 1 ${ATTRIBUTE(startdateSQL)} ${ATTRIBUTE(enddateSQL)}
parameters:
- if: PARAM(startdate) != NULL()
param: PARAM(startdate)
- if: PARAM(enddate) != NULL()
param: PARAM(enddate)
Database Response
Every result from a fulfillment (also database results) are transformed into a JSON and accessible with the VALUE method.
The result from a database is transformed in the following JSON set to work with:
id | title | description |
---|---|---|
1 | News 1 | Newsttext 1 |
2 | News 2 | Newstext 2 |
[
{
"id": 1,
"title": "News 1",
"description": "Newsttext 1"
},
{
"id": 2,
"title": "News 2",
"description": "Newsttext 2"
}
]
A database query always produces a JSON Array, because we don´t know how many results are returned in a query. Meaning in your JSONpath you have to use $[0].val1 if you are sure only one item is returned.
Generated Keys
When running an INSERT or UPDATE statement APICHAP will always get the Generated Key
from the database.
Depending on your database the Generated Key
looks differently:
Database | Get Generated Keys |
---|---|
MariaDb | If you run an INSERT command with an auto-incremental ID, you can use VALUE(fulfillment_name, $.insert_id) afterward which provides the generated id. |
PostgreSQL | PostgreSQL will deliver all columns that have been created in the INSERT/UPDATE query. Just like if you would query a select. VALUE(fulfillment_name, $.id) |
MSSQL | You will get the auto-incremental ID as GENERATED_KEYS. e.g.: VALUE(fulfillment_name, $[0].GENERATED_KEYS)) |
Request
A request is specified by setting a ´request´ attribute in your yaml.
Within this ´request´ attribute you are able to set the following attributes:
Field | Description | Mandatory |
---|---|---|
url | The URL for the request to send. | x |
httpType | Your httpType from our supported list: GET, POST, PUT, DELETE. | x |
contentType | The contentType specifies | |
header | Set http header parameter in the header section. | |
schema | Specify an schema to send with the requestbody. |
|
multipart | Send a multipart request | |
formBody | Send a formdata request | |
csvSeparator | Set the csvSeperator if you are reading or writing csv. | |
allowFailure | Allow failure of an request. (Default: false) | |
ignoreCert | Disable SSL Check (Default: False) | |
timeout | Change the default timeout in seconds (Default: 10 seconds) | |
gzip | Force to decrypt the response with gzip. (Default: False) |
ContentType
Specify which contentType you are planing to send:
application/json
text/xml
application/xml
text/csv
text/plain
Your request will be automatically transformed in your requested dataformat. Take a look at the Dataformat section to understand the process behind it.
Header
Specify headers by using a ´Key: Value´ approach.
instructions:
- request:
url: "https://www.example.com/news"
httpType: GET
contentType: application/json
header:
- "Content-Language: ${TEXT(en)}"
- "X-UserId: ${VALUE(ff1, $.userid)}"
Sending a Requestbody
Find more information on how to build your schema
here.
instructions:
- request:
url: "https://www.example.com/user"
httpType: POST
header:
- "Authorization: ${PARAM(Authorization)}"
contentType: application/json
schema:
email: PARAM(email)
name: PARAM(name)
userType: TEXT(user)
phone:
list: LIST(1)
item:
id: PARAM(phone)
Sending a Multipart Requests
If you need to send a multipart request you can do so by specifying the multipart
attribute.
This example shows you how you are using an incoming multipart file within the key image and send it to another request.
instructions:
- request:
url: https://www.example.com
httpType: POST
header:
- "Content-Type: image/jpeg"
multipart:
filepart:
contentType: TEXT(plain/text)
file:
name: TEXT(file.txt)
content: TEXT("Hello World")
jsonpart:
contentType: application/json
schema:
hello: TEXT(world)
Sending a Form Data Requests (formBody)
If you need to send a form data request you can do so by specifying the formBody
attribute. This section allows you to easily add key-value pairs to your form body request.
instructions:
- request:
url: https://www.example.com
httpType: POST
formBody:
key1: VALUE(val)
key2: TEXT(val2)
AllowFailure
By default when a request does not deliver a sucessfull statuscode the process is aborted. You are able to change that by setting the flag 'allowFailure'. With allowFailure: true the process will continue. Along with a method STATUSCODE() you are able retrieve the result status code of the fulfillment.
# With allowFailure: true, the operation will continue even if the request fails.
- request:
url: "https://www.example.com/user"
httpType: GET
header:
- "Authorization: ${PARAM(Authorization)}"
allowFailure: true
With using the STATUSCODE() method after the fulfillment you are able to request the status.
- name: check_status
type: generic
exceptions:
- if: STATUSCODE(ff_name) != 200
# Handle failure case
File
When using an FTP datasource you should build a file fulfillment. The FTP datasource supports the following variables when working with files:
folderPath
: The folder path on the FTP server.filePath
: The file path (including the file name) on the FTP server.contentType
: The MIME type of the file (e.g.,application/xml
).csvSeparator
: If working with CSV files, specify the separator.move
: The path to move the file after processing (optional).schema
: A map containing the schema for reading or writing files.
Creating a file
- type: update
datasource: myftp
instructions:
- file:
filePath: /xxx/File_${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}-${ATTRIBUTE(shipmentID)}.xml
contentType: application/xml
schema:
xxxx:
Listing files in a folder
Reading a file in a loop
- name: files_loop
type: loop
loop: VALUE(files, $)
fulfillments:
- name: myproducts
type: read
datasource: myftp
instructions:
- file:
filePath: /${VALUE(files_loop, $.filename)}
contentType: application/xml
exceptions:
- if: SIZE(VALUE(myproducts, $.response.xml.artikel)) <= INTEGER(0)
statuscode: 404
message: File not available or empty
Moving a file
- type: update
datasource: myftp
instructions:
- file:
filePath: /${VALUE(files_loop, $.filename)}
move: /processed/${DATEFORMAT(NOW(), "yyyy-MM-dd'T'HHmmss")}_${VALUE(files_loop, $.filename)}
You can specify the CSV separator using the
csvSeparator
field, and the content type for CSV files should betext/csv
.
Attributes
With this feature, you can define and store attributes (like variables in programming) within each fulfillment. These attributes can hold values such as strings, numbers, or other data types, and can be reused later in the operation.
How to Define Attributes
Attributes can be defined within any fulfillment and will be available throughout the operation after they are created. For example:
- name: update_project_status
type: UPDATE
datasource: middleware_db
instructions:
- sql:
query: UPDATE project SET status = 'failure' WHERE uuid = ?
parameters:
- param: PARAM(projectid)
attributes:
- status: VALUE(update_project_status, $[0].status)
In this example, the attribute status
is created and assigned the result of the VALUE
function, which can be reused later in the operation.
Conditional Attributes
You can also define attributes conditionally by using an if
clause inside the attributes section:
- type: GENERIC
attributes:
- if: PARAM(lang) == TEXT(DE)
language: TEXT(GERMAN)
org_lang: TEXT(DEUTSCH)
- another_attt: TEXT(test)
Here, the attributes language
and org_lang
are only set if the condition PARAM(lang) == TEXT(DE)
evaluates to true.
Using Attributes
Once an attribute is defined, it can be referenced later in the operation using the ATTRIBUTE
function:
For example, you can use ATTRIBUTE(org_lang)
to retrieve the value of the org_lang
attribute defined earlier.
Loop
Sometimes you need to run a list of fulfillments in a loop. For example if you have a requestbody and need to process each entry. This is possible by defining a loop with a list of sub fulfillments. A loop fulfillment holds the following attributes:
Field | Description | Mandatory |
---|---|---|
name | Set a fulfillment name. You may use the name later to use your fulfillment results in conditions or response building. | x |
type | LOOP | x |
loop | Define a JSONArray you want to loop through. | x |
fulfillments | In combination with loop you are able to define a list of fulfillments to run in a loop | x |
Within a loop you are able to get the current loop json by using the loop´s fulfillment name.
A loop that wants to insert every entry of the POST requestbody array would look like this:
'post /news/bulk':
fulfillments:
- name: loop_news_body
type: LOOP
loop: BODY($.news)
fulfillments:
# Insert the news entry
- name: news-insert
type: UPDATE
datasource: mydatabase
instructions:
- sql:
query: INSERT INTO news (id, title) VALUES (?,?)
parameters:
- VALUE(loop_news_body, $.id)
- VALUE(loop_news_body, $.properties.title)
Fulfillment Results within a loop are not available outside of the loop.
Loopbreak
Let´s assume you are calling a paged GET /products request in a loop until your paged request delivers no more results. A typical case for a loopbreak. A loopbreak can be set to each fulfillment (not the loop fulfillment directly) and holds a list of conditions. After the fulfillment is ran it checks if any of those conditions are true and if so aborts the loop and continues with the next fulfillments outside of it.
# Call a paged request by using loop(10) but abort the loop as soon as the request page is empty.
- name: loop_ff
type: loop
loop: LIST(20)
fulfillments:
# Retrieve inventory per page
- name: get_inventory
type: READ
datasource: api
instructions:
- request:
url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
httpType: GET
loopbreak:
- SIZE(VALUE(get_inventory, $.inventory)) <= INTEGER(0)
Index
If a fulfillment retrieves data and is run multiple times because its part of a loop you need a way to reference which data you want to access afterwards. Therefore define the index
value to an unique identifier. Afterwards you are able to access specific data results of the fulfillment using the VALUE_BY_INDEX method.
- name: loop_ff
type: loop
loop: VALUE(products, $)
fulfillments:
# Retrieve inventory per page
- name: get_inventory
type: READ
datasource: api
instructions:
- request:
url: "https://www.example.com?page=${VALUE(inventory_page, $)}"
httpType: GET
index: VALUE(@, $.id)
Fulfillment Type: if
The fulfillment type if
allows conditional execution of fulfillments based on a logical expression. Depending on the result of the condition, different sets of fulfillments can be run for both the then
and else
cases.
Do not confuse this with the simple
if
condition available for every fulfillment. Aif
fulfillment type is usefull if you have more fulfillment depending on something.
Structure
type: if
: Indicates that this fulfillment checks a condition.if
: The condition to evaluate. If this condition istrue
, the fulfillments underthen
will execute. Otherwise, those underelse
will execute.then
: A list of fulfillments to run if the condition is true.else
: A list of fulfillments to run if the condition is false.
- type: if
if: SIZE(VALUE(gen_config, $.data.operations)) <= INTEGER(0)
then:
# Config could not be created
- name: update_project_status
type: UPDATE
datasource: middleware_db
instructions:
- query: UPDATE project SET status = 'failure' WHERE uuid = ${PARAM(projectid)}
attributes:
- status: VALUE(update_project_status, $[0].status)
else:
- type: UPDATE
datasource: middleware_db
instructions:
- query: INSERT INTO config_response (project_id, action_generation) VALUES
(
${PARAM(projectid)},
${GET_JSON(gen_config)}
)
Fulfillment Type: GENERIC
The GENERIC
fulfillment type is designed to handle non-data operations, such as checking conditions or setting attributes. It cannot be used to read from or write to a datasource, making it useful for logic-only operations within the flow of an operation.
Usage:
- No
instructions
related to reading or writing data are allowed. - Primarily used for setting attributes, performing validations, or conditional checks.