Operations
Within the operations sections you should create an operation for each endpoint of your API. The name of the operation must match the name of the openapi specification, additionally containing the HTTP-method directly in the name. Leading to the following structure:
'HTTP-Method /ENDPOINT_PATH'
HTTP-METHOD | Endpoint | Name Pattern |
---|---|---|
GET | /news | 'GET /news' |
POST | /news/{id} | 'POST /news/{id}' |
DELETE | /news/comment/{id} | 'DELETE /news/comment/{id}' |
An operations consists of two sections:
Fulfillments
A fulfillment is the mechanism used to retrieve and process data for each operation in APICHAP. Everytime an endpoint from APICHAP is called, it will run all fulfillments specified for this operation after each other. A fulfillment will mostly gain a set of data which can then be used in all upcoming fulfillments and the responses.
A fulfillment reading data from a database could look like that:
- name: ff2
type: read
datasource: db
instructions:
- if: PARAM(language) == TEXT(en)
query: Select * from news WHERE lang = :[PARAM(language)]
- query: Select * from news
exceptions:
- if: SIZE(VALUE(ff2, $.id)) == INTEGER(0)
statuscode: 404
message: "No news entries available"
A fulfillment contains 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 | Define the type of your fulfillment. READ, UPDATE or LOOP | x |
datasource | The datasource this fulfillment should use to retrieve data from. Use the datasource name from the datasources section. | x |
if | Specifies an if condition. The fulfillment should only be processed under certain conditions. | |
instructions | Define your instructions. An instruction is the actual command on how to retrieve data from your datasource. Like a SQL query, Http-Call, ... | x |
exceptions | Use exceptions to immediately return a HTTP response to the user. | |
loop | Define a JSONArray to loop through and process each entry with a list of sub fulfillments. | |
fulfillments | In combination with loop you are able to define a list of fulfillments to run in a loop | |
loopbreak | A list of conditions. If they match, the current loop is aborted and apichap will continue with the next fulfillment. | |
index | If you run a fulfillment multiple times in a loop, you are able to index each iteration by a value. |
IF
Sometimes you want to process a fulfillment only on a certain condition. For example, if the input Header-Parameter 'Content-Language' is available.
# 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)
Instructions
Datasource
The structure of an instruction depends on your datasource Type:
Read more information on our supported datasource´s here: Datasources
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.
Field | Description | Mandatory |
---|---|---|
if | Define an if condition under which you want to use this query | |
query / request | Depending on your datasource you either have a query, request, ... | x |
Database
A instruction for a database type is fairly easy as you simply provide the SQL query. Flexible parameters are set using the APICHAP placeholder syntax.
The APICHAP placeholder syntax is secured against SQL Injections
instructions:
# The first query will run if language is set. Get news by input parameter Content-Language
- if: PARAM(Content-Language) != NULL()
query: SELECT id FROM news WHERE lang = :[PARAM(Content-Language)]
# This query is only called if no query before was already matched and executed.
- query: SELECT id FROM news
REST Request with JSON response
A REST 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 |
header | Set http header parameter in the header section. | |
body | Optional you may specify a requestbody for POST, PUT, ... requests. | |
multipart | Send a multipart request |
Header
Specify headers by using a ´Key: Value´ approach.
instructions:
- request:
url: "https://www.example.com/news"
httpType: GET
header:
- "Content-Language: :[TEXT(en)]"
- "X-UserId: :[VALUE(ff1, $.userid)]"
Requestbody
Building a requestbody for your request is possible by using the following fields:
Field | Description | Mandatory |
---|---|---|
contentType | Specify the format of your schema. Currently only "json" is supported | x |
schema | Find out how to construct your requestbody schema here: JSON Schema | x |
A instruction calling a POST request could look like this
instructions:
- request:
url: "https://www.example.com/user"
httpType: POST
header:
- "Authorization: :[PARAM(Authorization)]"
body:
contentType: json
schema:
email: PARAM(email)
name: PARAM(name)
userType: TEXT(user)
phone:
list: LIST(1)
item:
id: PARAM(phone)
Multipart
If you are in need of using a multipart for sending your request you can simply do so with the attribute multipart. 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:
file: MULTIPART_FILE(image)
imagesize: PARAM(imagesize)
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 | x |
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:
- query: Select * from news WHERE id = :[PARAM(id)]
exceptions:
- if: SIZE(VALUE(ffnewsdetail, $)) <= INTEGER(0)
statuscode: 404
message: "Item is not available"
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: VALUE(BODY(), $.news)
fulfillments:
# Insert the news entry
- name: news-insert
type: UPDATE
datasource: mydatabase
instructions:
- query: INSERT INTO news (id, title) VALUES (:[VALUE(loop_news_body, $.id)], :[VALUE(loop_news_body, $.properties.title)])
Fulfillment Results within a loop are not available outside of the loop.
Using loop on an object to read a map
If you have a json dictonary and need to loop through each key, you are able to use loop. The loop method will then transform your json to parse into the following structure using key for the json attribute name and val for the value.
{
"water": {
"id": 12,
"bottle_size": "0.7"
},
"cola": {
"id": 13,
"bottle_size": "0.3"
},
"orange_lemonade": {
"id": 14,
"bottle_size": "0.5"
}
}
Each entry is now transformed into a key and val value.
Field | Description |
---|---|
key | The json attribute name |
val | The value of the attribute |
You can now use this key & val for your loop:
'post /drinks':
fulfillments:
- name: loop_drinks
type: LOOP
loop: VALUE(BODY(), $)
fulfillments:
# Insert the news entry
- name: news-insert
type: UPDATE
datasource: mydatabase
instructions:
- query: INSERT INTO drinks (id, name, size) VALUES (:[VALUE(loop_drinks, $.val.id)], :[VALUE(loop_drinks, $.key)], :[VALUE(loop_drinks, $.val.bottle_size)])
Loopbreak
A list of conditions. If they match, the current loop is aborted and apichap will continue with the next fulfillment.
# 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 you run a fulfillment multiple times in a loop, you are able to index each iteration by a value. It makes sense to index your fulfillment in a loop by an id or unique value. Afterwards you are able to retrieve the results 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)
Responses
After all fulfillments have been executed you are able to build the response for your endpoint. You may specify a list of responses as you are able to use different kind of responses depending on the status. A response has the following attributes:
Field | Description | Mandatory |
---|---|---|
name | A name for identification | x |
default | True or False. Each operation must have a default response. The default response is used if no error occoured. | x |
statuscode | The http statuscode you want to return. | x |
schema | Find out how to construct your json schema here: JSON Schema |
Find out how to construct your json schema here: JSON Schema
# A simple response sending http statuscode 200 and building a simple Json
responses:
- name: success
statuscode: 200
default: true
schema:
id: VALUE(contactFF, $.results[0].id)
firstname: VALUE(contactFF, $.results[0].properties.firstname)
lastname: VALUE(contactFF, $.results[0].properties.lastname)
email: VALUE(contactFF, $.results[0].properties.email)
last_sync: VALUE(contactFF, $[0].update_time)
Full Example
A full operation to get a news item from a database could look like this:
'get /news/{id}':
fulfillments:
- name: news
type: READ
datasource: db
instructions:
- query: Select id, title from news WHERE id = :[PARAM(id)]
- name: news_images
type: READ
datasource: db
instructions:
- query: Select imageurl from news_images WHERE imageId = :[PARAM(id)]
responses:
- name: success
statuscode: 200
default: true
schema:
id: VALUE(news, $[0].id)
title: VALUE(news, $[0].title)
images:
list: VALUE(news_image, $)
item:
imageurl: VALUE(@, $.imageurl)
Triggers
It´s possible to run an operation also by using timebased triggers. Triggers can be used additionally to an working endpoint operation, as well as by a standalone (not used as endpoint) operation. In the second case, the operation will not need a response configuration.
A trigger consists of:
Field | Description | Mandatory |
---|---|---|
name | A name for identification | x |
cron | A timebased cron condition: 0 0 * * * * | x |
# Run this operation every minute
'only_timebased_operation':
triggers:
- name: minutely_trigger
cron: 0 * * * * *
fulfillments:
- name: do something in this fulfillment
type: READ
....
# This operation will be triggered by the request GET /test as well as timebase every hour.
'GET /test':
triggers:
- name: every_hour
cron: 0 0 * * * *
fulfillments:
- name: do something in this fulfillment
type: READ
....
responses:
# As this operation has an HTTP trigger (GET /test) it needs a response.