ITENTIAL ADAPTERS TECHNICAL RESOURCE
Adapter Action.json
Defining Actions in an Adapter
- The action.json file defines the actions that an adapter can take with the system it is integrating with. Actions are oftentimes referred to as integrations, APIs, endpoints, etc.
- When building an adapter from Swagger, each action within Swagger will result in an action somewhere within the action.json of an adapter.
- Actions are grouped together within an entity. In other words, each entity that is defined within the adapter will contain a single action.json file that contains all the possible actions you can take on the entity.
- Some examples of actions include CRUD operations on an entity. For example, if you have a device entity, typical actions would be getDevice, createDevice, updateDevice, and deleteDevice. Other actions can also be defined for an entity. For example, getDeviceConfig and setDeviceConfig.
- There are no limitations to the number of actions an entity can have.
- Changes to the action.json do not require an Itential Automation Platform (IAP) or adapter restart.
Action Field Definitions
- name (required): This is the name of the action. When the method in adapter.js wants to take this action in the other system, it calls identifyRequest and provides the entity (first parameter) that this action is in and the name (second parameter) to identify the particular action.
- protocol (required): REST is currently the only protocol supported but this field can support others. SOAP would still use REST as it works via HTTP/HTTPS via a POST call.
- method (required): This is the REST method (sometimes referred to as a verb) the call uses. The most common are GET, POST, PUT, PATCH and DELETE but others are supported as well.
- entitypath (required): This is the path that is added to the API call – https://host:port/entitypath. It is the added part of the call. You will notice it has variables in it {base_path}. These variables are replaced by the adapter library when the call is made.
- schema: Default schema used for both the request and response, unless overridden by a specific schema.
- This field is used to define the data that is sent between the adapter and the other system. You can have a different schema definition for every call in an action file. You may also have different schemas for the request and the response. Consequently you may see various schema files within an entity.
- requestSchema: Defines a schema specific to the request.
- responseSchema: Defines a schema specific to the response.
- timeout (optional): While the adapter provides a property to set a global timeout, sometimes a particular action may take a long time. This property provides the ability to set a timeout on the action which overrides the global timeout.
- If no value is provided or it has a value <= 0, the global request timeout will be used.
- sendEmpty (optional): This flag tells the adapter there is no information to send and to still send an empty object (e.g.{}). The default is to not send any data.
- sendGetBody (optional): This flag tells the adapter whether to send a body on a GET request. The default is no body on GET.
- datatype (optional): This required field is used to define the type of data that is sent between the adapter and the other system. Can be used for the request or response, or both. Similarly to schemas, you may have different datatypes for the request and the response. Some example datatypes include PLAIN, XML, XML2JSON, URLENCODE, FORM and JSON.
- If no datatype is provided, JSON is used as the default.
- requestDatatype: Defines the datatype of the request.
- responseDatatype: Defines the datatype of the response.
- headers (optional): Many systems may require additional headers, and there are several ways to specify headers on the request. If the headers are always the same for a particular action, specifying them on the action is the best method. Other ways to specify headers include:
- Global request properties – These headers are consistent on all requests.
- Additional headers – These headers can be static for a call or dynamic by allowing them to be set based on user input.
- Some headers are set by the adapter libraries based on the datatype, but those can be overridden if you specify them elsewhere.
- responseObjects (required): Tells the adapter how to handle the response. Some actions may have many responses, so this field is often used for different purposes.
- First, it provides a way to tell the adapter where in the response to find the data that IAP cares about.
- Second, this field is crucial for standalone testing because it tells the adapter libraries where to find mock data that it should return when not integrated with the other system.
- type: Used by the adapter libraries to identify which response object to use. The adapter libraries will go through a hierarchical process to match the best response type for a specific request. This field is required.
- key: Used by the adapter libraries to tell the adapter where in the response to find the data that IAP is concerned with. It supports JSONquery. It allows you to remove metadata, etc. and only return the data that you really care about.
- mockFile: This is used by the adapter libraries when running in stub mode (not integrated) to tell the adapter where the mock data that should be returned is located.
ACTION.JSON
{
"name": "getIP",
"protocol": "REST",
"method": "GET",
"entitypath": "{base_path}/{version}/addresses/{pathv1}",
"schema": "schema.json",
"timeout": 3000,
"sendEmpty": true,
"sendGetBody": false,
"datatype": "PLAIN",
"headers": {},
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": ""
}
]
},
Usage & Examples
Entity Path Variables
- {base_path}: Replaced with the global base_path property. Allows for easier maintenance if the base path changes.
- {version}: Replaced with the global version property. Allows for easier maintenance if the version changes.
- {pathv#}: Replaced with the path variables provided in the uriPathVars array in the individual request.
- pathv1 corresponds to the 1st (0) element in the array, and so forth.
- {query}: Replaced with any information provided in the uriQuery object on the individual request. The adapter library automatically assigns a query string to this object and adds it to the end of the path.
ACTION.JSON
{
"name": "getDeviceInterface",
"protocol": "REST",
"method": "GET",
"entitypath": "{base_path}/{version}/device/{pathv1}/interface/{pathv2}?{query}",
"schema": "schema.json",
"timeout": 3000,
"sendEmpty": true,
"sendGetBody": false,
"datatype": "PLAIN",
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": ""
}
]
},
Entity Path Examples
- For the first path, it might work for getting all devices, but what about a specific device based on some id?
- If the other system uses the same endpoint, you can modify the entitypath (second path) and use the same action for both calls. Now what about filtering?
- Once again, if the other system uses the same endpoint, we can modify the entitypath (third path) and use the same action still.
- What if I have multiple path variables? The adapter supports many path variables (fourth path) if the # corresponds to the element in the uriPathVars array (1 -> 0, etc.).
ACTION.JSON
"/api/v1/devices"
"/api/v1/devices/{pathv1}"
"/api/v1/devices/{pathv1}?{query}"
"/api/v1/devices/{pathv1}/interface/{pathv2}?{query}"
- You can hard code the version in a specific action call and in all action calls. However, it is easier to maintain/upgrade to a new version if this is set in a global property. Then the entity path can use the global property (first path).
- You can still support unique actions that use a different version by hard coding that version in the second path. This allows the adapter to support integration to many different versions of APIs by simply using a property for the predominant version and then hardcoding other versions.
ACTION.JSON
"/api/{version}/devices/{pathv1}?{query}"
"/api/v2.0/devices/{pathv1}?{query}"
- Base Path works like version; you can hard code the base_path in any and all action calls. However, it is easier to maintain/upgrade to a new base_path if this is set in a global property. Then the entity path can use the global property (first and second paths). You can still support unique action calls that use a different base_path by hard coding that base_path (third and fourth paths).
- Note: Remember the entitypath is set up to support a great deal of flexibility in the API calls. This ranges from easy maintenance to providing dynamic information on each call.
ACTION.JSON
"/{base_path}/{version}/devices/{pathv1}?{query}"
"/{base_path}/v2.0/devices/{pathv1}?{query}"
"/api/{version}/devices/{pathv1}?{query}"
"/api/v2.0/devices/{pathv1}?{query}"
Different Schemas
- This example shows the use of a different request and response schema. There are different scenarios where you may want to do this.
- One scenario is when you want to validate required information on the request that may not exist in the response.
- Another scenario is when the information in the request is very different from the response and you do not want to make the schema too complex.
ACTION.JSON
{
"name": "getToken",
…
"requestSchema": "reqTokenSchema.json",
"responseSchema": "respTokenSchema.json",
…
}
REQUEST SCHEMA
{
"$id": "reqTokenSchema.json",
"type": "object",
"schema": "http://json-schema.org/draft-07/schema#",
"translate": true,
"dynamicfields": true,
"properties": {
"ph_request_type": {
"type": "string",
"description": "type of request (internal to adapter)",
"default": "getToken",
"enum": [
"getToken",
"healthcheck"
],
"external_name": "ph_request_type"
},
"username": {
"type": "string",
"description": "username to log in with",
"external_name": "user_name"
},
"password": {
"type": "string",
"description": "password to log in with",
"external_name": "passwd"
}
},
"required": ["username", "password"],
"definitions": {}
}
RESPONSE SCHEMA
{
"$id": "respTokenSchema.json",
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"translate": true,
"properties": {
"ph_request_type": {
"type": "string",
"description": "type of request (internal to adapter)",
"default": "getToken",
"enum": [
"getToken"
],
"external_name": "ph_request_type"
},
"token": {
"type": "string",
"description": "the token returned from system",
"external_name": "access_token"
}
},
"definitions": {}
}
Different Data Types
- PLAIN – Used for plain/text. This datatype is never translated.
- XML – Used for application/xml. This datatype is not translated.
- XML2JSON – Used for application/xml but unlike XML, this datatype is translated from JSON to XML going out and XML to JSON coming in.
- URLENCODE – Used for URL encoding of the data sent to the other system.
- FORM – Used to send data to the other system in the format of a form.
- JSON – Used for application/json. This datatype is translated based on the schema definition.
- JSON is the default. If a datatype is provided that is not supported, then the field is set to PLAIN as that is less impactful.
ACTION.JSON
{
"name": "getIP",
"protocol": "REST",
"method": "GET",
"entitypath": "{base_path}/{version}/addresses/{pathv1}",
"schema": "schema.json",
"timeout": 3000,
"sendEmpty": true,
"sendGetBody": false,
"datatype": "PLAIN",
“requestDatatype”: “PLAIN”,
“responseDatatype”: “XML”,
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": ""
}
]
},
Individual Action Timeout
- Although the default is to use the global request.attempt_timeout property, you can provide a timeout on a particular action that overrides the request timeout for all actions.
- An action timeout is useful when you know a certain action may take a lot longer (or it may take less time) to complete than most typical responses.
- 5000ms is the default value for the action timeout field.
ACTION.JSON
{
"name": "getIP",
"protocol": "REST",
"method": "GET",
"entitypath": "{base_path}/{version}/addresses/{pathv1}",
"schema": "schema.json",
"timeout": 3000,
"datatype": "PLAIN",
"sendEmpty": true,
"sendGetBody": false,
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": ""
}
]
},
Finding Response Objects
This example illustrates how you can set the responseObjects field to find and limit data in a response.
- The key in the responseObjects tells the adapter where to find the response data. This field should be a JSON Query string. This provides a more powerful way to find and limit the data in the response to IAP.
- This is a STATIC setting; it is only used to LOCATE data when it is always in the same place.
ACTION.JSON
{
"name": "getIP",
"entitypath": "{base_path}/{version}/addresses/{pathv1}?{query}",
….
"responseObjects": [
{
"type": ”anykey",
"key": "result[id=88911]",
"mockFile": ”a.json"
},
{
"type": ” anykey",
"key": "result[4]",
"mockFile": ”b.json"
},
{
"type": ” anykey",
"key": "result.goodData",
"mockFile": ”c.json"
}
]
},
Multiple Mock Data Files
The mock data files defined within the action.json provide a way for the adapter to be tested in standalone mode without integrating with other systems. These files also provide results that mimic the data it expects to receive when connected to the other system.
The data hierarchy in a mock data file is:
- Match data in the body.
- Match data in a path variable.
- Match data in a query or option.
- If there is a body (withBody)
- If there is a path variable (withPathv#).
- If there is a query (withQuery).
- If there is an option (withOption).
- Otherwise use the default.
- On a call with a body {name: ‘abc123’ } the a.json would be returned.
- On a call with a path variable (http://system:80/path/ver/addresses/error) the b.json would be returned.
- On a call with a query variable (http://system:80/path/ver/addresses?name=happy) the c.json would be returned.
ACTION.JSON
{
"name": "getIP",
"entitypath": "{base_path}/{version}/addresses/{pathv1}?{query}",
….
"responseObjects": [
{
"type": "name-abc123",
"key": "",
"mockFile": "a.json"
},
{
"type": "error",
"key": "",
"mockFile": "b.json"
},
{
"type": "name=happy",
"key": "",
"mockFile": "c.json"
}
]
},
- Can still support different mock data based on the URI structure:
- path variables (withPathv#)
- query (withQuery)
- On a call with a path variable (http://system:80/path/ver/addresses/abc123) the y.json is returned
- On a call with a query variable (http://system:80/path/ver/addresses?name=abc123) the z.json is returned.
- Otherwise the x.json is returned.
ACTION.JSON
{
"name": "getIP",
"entitypath": "{base_path}/{version}/addresses/{pathv1}?{query}",
….
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": "x.json"
},
{
"type": "withPathv1",
"key": "",
"mockFile": "y.json"
},
{
"type": "withQuery",
"key": "",
"mockFile": "z.json"
}
]
},
Deprecations
- There are fields in the action.json that are still supported in the code base but, as a best practice, are no longer used.
- The querykey deprecation was used to specify the key to insert into a request prior to the query. It is no longer used. Instead the query key should be inserted directly into the entitypath.
- /{base_path}/{version}/devices/{pathv1}?myquery={query}
- The adapter library will automatically remove the query key if no query data is provided; thereby making the above path /base/ver/devices/var1 on a request where there is no query.
- If there is no querykey and no querykey (e.g., ?myquery=) inserted into the entitypath, a `?` will be inserted prior to the query by the adapter library. For example:
- For example:
- If the entitypathwas /{base_path}/{version}/devices/{pathv1}{query}and a query was provided, the path for the request will be /base/ver/devices/var1?query.
- In this example, the entitypathis properly constricted and easier to understand.