ITENTIAL ADAPTERS TECHNICAL RESOURCE
Adapter Schema
What is Schema?
Defining Data to and from an Adapter
Schema is used to define the data that an adapter sends to and receives from the system it is integrating with. It does not define the type of data (see datatype in action.json). It is only utilized when the datatype is something that can be translated; for example, JSON.
Multiple schema files can be utilized by an adapter. Every action within the adapter can have a:
- Request schema which defines the data the adapter will send to the other system on the API request.
- Response schema which will define the data that the other system will send to the adapter on the response.
The request and response do not have to be separate schemas; on CRUD operations they will often be the same schema. When building an adapter from Swagger, each action within the Swagger will have query or body parameters defined. These are inserted into the schema files.
There is a good reason to define your schema files. Multiple systems often have a different way of defining entities. Itential Automation Platform (IAP) will have its way and often the other systems will not conform to that. Thus, you need the ability to translate data between them.
Take the following example:
- If System A defines a device IP address as ip_addr and System B defines it as ipaddress and IAP defines it as ip_address then something needs to map the fields so that when we get a device from System A it takes ip_addr and puts it into ip_address for IAP.
- Similarly, when IAP wants to add a new device to System B we take ip_address and put it in ipaddress. This translation happens automatically in the adapter libraries based on the information that you put in the schemas.
Schema Field Definitions
Schema files are based on a JSON schema with some added fields utilized by the adapter libraries. So if it works in the JSON schema, it should work in the schema file.
- $id (required): This is the id/name of the schema and should be unique.
- type (optional): Used in several places, it is the data type associated with a specific schema item. If you want type validation, then this field should be provided.
- schema (required): This is the JSON schema specification used by the adapter’s schema file. The adapter uses Ajv to check the data against the schema so this specification should be one that is supported by Ajv.
- description (optional): Used in many places, it is the description associated with a specific schema item.
- translate (optional): This field is added by the adapter. It is available on any object type field. It tells the adapter library whether to run translation (or not) on the schema or object. Adapters will often be generated with an empty schema file and translation set to false as that is a simpler schema file. It defaults to true on the schema and is inherited from parents on objects within the schema.
- dynamicfields (optional): This field is also added by the adapter. It is available on any object type field. It tells the adapter library whether to include any undefined fields in the data that it returns from the translator. It defaults to false on the schema and is inherited from parents on objects within the schema.
- properties (required): This field contains all the data fields within the schema.
- ph_request_type (required): This property should be set in every adapter schema file. It is never included with the data but used internally by the adapter library. It should have an enum that includes every action that uses this schema file. The default should be set to one of those actions.
- default (optional but required with ph_request_type): Defines the default value for the data field. In the case of ph_request_type it must be one of the values in the enum. In general, be careful using default as it is possible to get data that you do not want inside your request/response. It is also best to be careful when using certain data rules (e.g., min/max). Make sure they are appropriate for the request/response.
- enum (optional but required with ph_request_type): Defines the values that a data field of type string can have. In the case of ph_request_type, it is important that enum include every action that uses this schema.
- Property names: These refer to how the data should be presented to IAP.
- In the earlier example, IAP referred to IP addresses as ip_address. Accordingly, that means the value in this field would be ip_address.
- In the case of token requests there should be a username property and a password property.
- external_name: This field is added by the adapter. It is how the data should be presented to the system we are integrating with. In the prior example, System A referred to IP address as ip_addr. So for this field, the value would be set to ip_addr.
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"
}
},
"definitions": {}
}
- parse (optional): This field is added by the adapter. It tells the adapter if this field needs to be independently parsed (maybe it was stringified within the object that was sent). The default is false.
- encode (optional): This field is added by the adapter. It tells the adapter whether to endode/decode the data contained within this field. Encoding is base 64. The default is not to encode.
- encrypt (optional): This field is added by the adapter. It tells the adapter whether to encrypt/decrypt the data contained within this individual field.
- Different encryption types are supported in the type field, but at the current time only AES encryption is used.
- The key field contains the key used to encrypt/decrypt the data in the field.
The default is not to encrypt.
SCHEMA
{
"$id": "reqTokenSchema.json",
"properties": {
….
"username": {
"type": "string",
"description": "username to log in with",
"parse": true,
"encode": false,
"encrypt": {
"type": "AES",
"key": "sfhgjhajlgsfhjlaghlshdg"
},
"external_name": "user_name"
},
….
},
"definitions": {}
}
- Required fields: You can have required fields within the schema using standard JSON schema techniques like having a list of “required” properties using a required value. You can also use more complex techniques for “required” properties based on the action.
- One advantage of using required properties is that invalid requests will not be sent to the other system, thereby causing the system to error. Instead, the error will be returned by the adapter. This technique is not only efficient but saves time!
SCHEMA
{
"$id": "sevone_alert",
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"ph_request_type": {
"type": "string",
"description": "type of request (internal to adapter)",
"default": "getAlerts",
"enum": [
"getAlerts", "getAlertsFiltered", "getAlertsForDevice",
"getAlertsForMapConnection", "getAlertsForMapNode",
"createAlert", "updateAlert", "assignAlert", "ignoreAlert",
"clearAlert", "deleteAlert"
],
"external_name": "ph_request_type"
},
"id": {
"type": "integer",
"description": "id of the alert in sevone",
"minimum": 0,
"maximum": 999999999999,
"external_name": “sys_id"
},
"origin": {
"type": "string",
"description": "where this alert was originated from",
"external_name": "origin"
}
},
"allOf": [
{
"if" : { "properties" : { "ph_request_type": { "enum": ["createAlert"] } } },
"then" : { "required" : ["origin"] }
}
],
"definitions": {}
}
Schema Usage & Examples
Defined in action.json
Schema is used to define the data that goes 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 response. Thus, you may see many schema files within an entity. You must have schema in the action.json file – so either the schema is required, or the requestSchema and responseSchema are required.
- schema: Default to use when there is no specific schema. Can be used for either the request or the response, or both.
- requestSchema: Defines a schema specific to the request.
- responseSchema: Defines a schema specific to the response.
Schema Files
The schema files should be in the location that was defined in the action.json. In this case, a file should exist in schema.json, requestSchema.json, and responseSchema.json. You can move all schemas into a subdirectory (e.g., mock datafiles). Be sure the references to the schema in action.json are changed accordingly.
ACTION.JSON
{
"name": "getIP",
"protocol": "REST",
"method": "GET",
"entitypath": "{base_path}/{version}/addresses/{pathv1}",
"schema": "schema.json",
"requestSchema": "requestSchema.json",
"responseSchema": "responseSchema.json",
"timeout": 3000,
"sendEmpty": true,
"datatype": "PLAIN",
"headers": {},
"responseObjects": [
{
"type": "default",
"key": "",
"mockFile": ""
}
]
},
Simple Schema
The Adapter Builder will often use a simple schema because it is less complex to understand, and it requires less input to generate. A simple schema will turn off translation – this means the data that is sent to IAP is all the data that is sent by the other system. In addition, all the data sent by IAP will be sent to the other system
SCHEMA
{
"$id": "deviceSchema.json",
"type": "object",
"schema": "http://json-schema.org/draft-07/schema#",
"translate": false,
"dynamicfields": true,
"properties": {
"ph_request_type": {
"type": "string",
"description": "type of request (internal to adapter)",
"default": "getDevice",
"enum": [
"getDevice ",
"createDevice"
],
"external_name": "ph_request_type"
}
},
"definitions": {}
}
Other Data and Translation
You can create a schema that includes some of the data that you want (e.g., for validation or to translate), and specify also that you want the other data to remain as is.
By setting dynamicfields to true, you are telling the translator that if the field is not defined, pass the data “as is”.
When the external_name is different than the property name, the adapter knows to translate or map the data in the objects that it sends and receives so that IAP receives the data in a field that matches the property name and the other system receives the data in a field that matches external_name. For example:
- {objectId: 123} goes to System A
- {componentId: 123} goes to IAP
SCHEMA
{
"$id": "sevone_alert",
"type": "object",
"schema": "http://json-schema.org/draft-07/schema#",
"translate": true,
"dynamicfields": true,
"properties": {
"ph_request_type": {
….
},
"deviceId": {
"type": "integer",
"description": "the id of the device this alert originated on",
"external_name": "deviceId"
},
"componentId": {
"type": "integer",
"description": "the id of the object/device componenet this alert originated on",
"external_name": "objectId"
}
},
"definitions": {}
}
Token Schemas
Token requests are a good example of a translation you may want in two schemas (request and response). For instance, you may want to require fields on the request, but those fields are not present on the response.
Notice this schema does the translation from username to user_name and password to passwd on the request, and from access_token to token on the response.
Since the response schema does not have the dynamicfields flag, the default is false – which means that the only field that will show up in the response to IAP is token.
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": ”respTokemSchema.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": {}
}
Schemas Sometimes Required
Sometimes sending requests to other systems can be costly. As a result, when we know that certain information is required and we do not have that information, it makes sense to error the request before it is sent.
Schema allows us to do this by setting the information that is required on certain actions. This example requires origin to be provided on createAlert.
The adapter will run Ajv validation on the data based on the schema. If the origin is not provided, the adapter will return an error without ever making the request to the other system.
SCHEMA
{
"$id": "sevone_alert",
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"ph_request_type": {
"type": "string",
"description": "type of request (internal to adapter)",
"default": "getAlerts",
"enum": [
"getAlerts", "getAlertsFiltered", "getAlertsForDevice",
"getAlertsForMapConnection", "getAlertsForMapNode",
"createAlert", "updateAlert", "assignAlert", "ignoreAlert",
"clearAlert", "deleteAlert"
],
"external_name": "ph_request_type"
},
"id": {
"type": "integer",
"description": "id of the alert in sevone",
"minimum": 0,
"maximum": 999999999999,
"external_name": “sys_id"
},
"origin": {
"type": "string",
"description": "where this alert was originated from",
"external_name": "origin"
}
},
"allOf": [
{
"if": { "properties": { "ph_request_type": { "enum": ["createAlert"] } } },
"then": { "required": ["origin"] }
}
],
"definitions": {}
}
You can also set the schema to say that “one of X” fields are required (as shown in the example on the right).
In this example, either the componentId or the deviceId are required to create the alert.
If they are both missing, then Ajv validation will result in an error which the adapter libraries will return without making the request to the other system.
SCHEMA
{
"$id": "sevone_alert",
….
"properties": {
"ph_request_type": {
….
"enum": [
"createAlert”
],
"external_name": "ph_request_type"
},
"id": {
…..
},
"deviceId": {
"type": "integer",
"description": "the id of the device this alert originated on",
"external_name": "deviceId"
},
"componentId": {
"type": "integer",
"description": "the id of the object/device componenet this alert originated on",
"external_name": "objectId"
}
},
"allOf": [
{
"if": { "properties": { "ph_request_type": { "enum": ["createAlert"] } } },
"then": {
"oneOf": [
{
"required": ["deviceId"]
},
{
"required": ["componentId"]
}
]
}
}
],
"definitions": {}
}
Parsing a Field Value
By using the parse flag in the schema, you can get a response like the one below where some of the data is stringified within the response. The individual fields will be parsed as well, so that they are returned as JSON.
SCHEMA
{
"$id": "reqTokenSchema.json",
"properties": {
….
"Data": {
"type": "object",
"properties": {
"Data": {
"type": "string",
"parse": true,
"external_name": "Data"
}
},
"external_name": "Data"
} ….
},
"definitions": {}
}
RESPONSE
{
"ResponseType": "SUCCESS",
"Data": {
"Message": "",
"MessageType": "Success",
"returnStatus": false,
"RecordCount": 0,
"Data": "[{\"siteid\":\"XXXXX\",\"ng_rtr_pair_nm\":\"XXXXXXX\",\"ngmadrtr_id1\":\"XXXXXX\",\"ngmadrtr_id2\":\"XXXXXX\",\"ng_rtr_port\":\"2/1/5\",\"eth_term_mso\":\"XXXXX\"}]"
}
}
Encoding a Field Value
Encoding a field value takes the value of the field and runs it through b64 encoding. The encoded result is placed back into the object that will be sent to the other system. It will also decode the response using b64.
If you do not want the response decoded, use a different schema for the request and the response.
To encode a field set the encode flag to true.
SCHEMA
{
"$id": "reqTokenSchema.json",
"properties": {
….
"Data": {
"type": "object",
"properties": {
"Data": {
"type": "string",
"encode": true,
"external_name": "Data"
}
},
"external_name": "Data"
} ….
},
"definitions": {}
}
REQUEST
{
"Data": {
"returnStatus": false,
"RecordCount": 0,
"Data": "something random"
}
}
RESPONSE
{
"Data": {
"returnStatus": false,
"RecordCount": 0,
"Data": "c29tZXRoaW5nIHJhbmRvbQ=="
}
}
Encrypting a Field Value
Encrypting a field value will take the value of the field and run it through an encryption technique. It will place the result back into the object that will be sent to the other system. It will also decrypt the response.
If you do not want to decrypt the response, use a different schema for the request and the response.
To encrypt a request, add the encrypt object with the appropriate type (currently AES only) and the key that should be used to encrypt/decrypt.
SCHEMA
{
"$id": "reqTokenSchema.json",
"properties": {
….
"Data": {
"type": "object",
"properties": {
"Data": {
"type": "string",
"encrypt": {
"type": "AES",
"key": "thisismykey"
},
"external_name": "Data"
}
},
"external_name": "Data"
} ….
},
"definitions": {}
}
REQUEST
{
"Data": {
"returnStatus": false,
"RecordCount": 0,
"Data": "something random"
}
}
RESPONSE
{
"Data": {
"returnStatus": false,
"RecordCount": 0,
"Data": "IcNev7McG3/8MOt8QaULRzkNjDdzUKwhf6vEqPZkhog="
}
}
Adapter Support
- For help with schema definitions:
- Create a ticket (ISD, IPSO or ADAPT).
- You can work the ticket or allow the Itential Adapter Team to verify if the defined schema can be supported.
- Once all changes are made, update the adapter-utils dependency in your adapter or run the adapterMigrator.
- To update your adapter-utils dependency:
- Change the version in the package.json dependencies.
- rm –rf node modules
- rm package-lock.json
- npm install
Get Started with Itential
Start a 30 day free trial, or contact us to discuss your goals and how we can help.