Skip to main content

Endpoints

Endpoint Configuration​

Each Endpoint is configured independently with a schema stored in MongoDB. The schemas are defined as JSON objects. Endpoints are added to the Mapping Mediator via its API. In the example below, we post a simple example endpoint to our local API endpoint which defaults to http://localhost:3003/endpoints.

curl --location --request POST 'http://localhost:3003/endpoints' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Endpoint Example",
"endpoint": {
"pattern": "/example",
"method": "GET"
},
"transformation": {
"input": "JSON",
"output": "JSON"
},
"inputMapping": {
"constants.example_constant":"hello"
},
"constants": {
"example_constant": "world!"
}
}'

This is a RESTful API that supports POST, PUT, GET, and DELETE. Therefore, once an Endpoint has been created it can be retrieved, updated, and deleted. For more information, please see the Mapping Mediator API documentation.

The schema can be broken down into 7 major areas of concern:

  1. Metadata
  2. Mapping
  3. Constants
  4. Validation
  5. Transformation
  6. Orchestration
  7. State

1. Metadata​

The metadata section contains the details involved in route setup. The following can be set in the root level of the config object:

  • Endpoint name
  • Endpoint description
  • Route path pattern
  • Route HTTP method
  • Expected input message data type
  • Desired output message data type
  • Kakfa Input Topic kafkaConsumerTopic

Route Path Pattern​

This is the path on which the OpenHIM Mapping Mediator will listen to trigger a specific message mapping transformation. The path is specified in the endpoint pattern property. Url parameters are supported. The URL parameters can be used in the external requests and in the mapping. A request that matches on a pattern like /path/:parameter1/:parameter2 will have the values of these parameters available for use in the external requests and mapping under variable names parameter1 and parameter2.

Expected Input​

Specify the expected input message type for this specific endpoint to allow the OpenHIM Mapping Mediator to successfully parse the incoming message for processing. Currently accepted formats are JSON and XML

Desired Output​

Specify the desired output message type for this specific endpoint to allow the OpenHIM Mapping Mediator to parse the outgoing message. Currently accepted formats are JSON and XML

Kafka Input Topic​

Specify the kafka topic to consume data from using property kafkaConsumerTopic.

2. Mapping​

The mapping schema is defined within the input-mappingfield. This section defines how the incoming data will be used to build up a new object in the desired structure. Our mapping is done using the node object mapper library.

Every mapping field in this section follows this pattern

{
"path.to.input.data": "path.to.output.field"
}

Data available for mapping​

All input data, populated from different sources, are stored in the following internal object structure:

{
"requestBody": {...},
"requestHeaders": {...}, // Only in versions above v2.3.1
"lookupRequests": {
"<id>": {
"headers": {...},
"data": {...} // response body
}
},

// In versions below v3.0.0 the lookupRequests has the following structure
"lookupRequests": {
"<id>": {...} // response body
},
"transforms": {...},
"constants": {...},
"urlParams": {...},
"state": {...},
"timestamps": {...}
}

See the full flow of examples to follow:

{
"requestBody": {
"status": "Active"
},
"lookupRequests": {
"location": {
"data": "Unknown",
"headers": {
"type": "live"
}
}
}
}

3. Constants​

The constants section contains data to be used alongside the client input data. This contains values for fields required in the output data that weren't available from the original client input.

Fields in constants can only be referenced in the transformation, orchestration and mapping sections of the Endpoint schema. See below for a simple example:

Here we define two constants.

"constants": {
"first_constant": "world",
"second_constant": 3.1415
}

4. Validation​

The data from the input request as well as any lookup requests can be validated before the mapping occurs. We use the ajv library to handle our validation. Below is a sample of a validation schema:

{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"weight": {
"type": "number",
"min": 0.1,
"max": 650,
"errorMessage": "Weight must be a number between 0.1 and 650"
}
},
"required": ["name"]
}

This schema will verify a message contains a required field called name of type string and an optional field called weight that if provided, is a number within the range 0.1 to 650.

For more details, see the validation page.

5. Transformation​

The transformation step allows data to undergo complex changes before the mapping step. The Mapping Mediator makes use of the JSONata library to perform transformations. Transformed data and the original input are both available to be mapped in the mapping step.

Note: Transformed data will not be included in the output data unless it is mapped to an output field

Here we define two input request fields.

{
"radius": "10",
"subject": "world"
}

For more details check out transformation

6. Orchestrations​

This feature allows for data lookups from external services and the sending of the mapped data to external services. The data to look up and the services where the result of the mapping should be sent are specified in the requests section. The data looked up is aggregated with the input data before the validation is done. Response data is not validated before being sent. For more orchestrations details please see the Orchestrations feature documentation. Below is an extract of some request orchestrations.

{
...
"requests": {
"lookup": [
{
"id": "dhis2",
"config": {
"method": "get",
"url": "https://play.dhis2.org/2.35/api/organisationUnits.json?paging=false",
"headers": {
"Content-Type": "application/json",
"Authorization": "Basic YWRtaW46ZGlzdHJpY3Q="
}
}
}
],
"response": [
{
"id": "hapi-fhir",
"config": {
"method": "post",
"url": "http://hapi.fhir.org/baseR4/",
"headers": {
"Content-Type": "application/json"
}
}
}
]
}
}

In the example above, the lookup request gets facility data from DHIS2. That data would then be mapped (schema not shown for brevity) and the output data sent to a FHIR server. These requests can include query and url parameters which can be extracted from incoming data. Chaining together endpoint calls using this orchestration mechanism can lead to very complex logic being implemented.

There are two types of external requests, the lookup and the response. Query and url parameters for the external request can be dynamically populated

You can fetch data to map. The retrieved data will be aggregated with the input data supplied in the request body. The following shows the aggregation

Lookup request:

{
"requests": {
"lookup": [
{
"id": "dhis2",
"config": {
"method": "get",
"url": "https://play.dhis2.org/2.35/api/organisationUnits.json?paging=false",
"headers": {
"Content-Type": "application/json",
"Authorization": "Basic YWRtaW46ZGlzdHJpY3Q="
}
}
}
]
}
}

The data retrived from the lookup request will be aggregated inside the lookupRequests object using its id as the nested field name. The full list off input data available for mapping is listed above.

  {
...
"lookupRequests": {
"dhis2": {
"data": {...},
"headers": {...}
}
}
}

7. State​

The state management configuration is a useful feature when the results of previous requests influence details within the current request. For example, say you want to poll for recent data within a range - using the endpoint state you could store when the last time this Endpoint was triggered and use that state value as the timestamp within your period request. Please see the feature documentation for an in-depth look at Endpoint State.

In the example below, we are extracting two fields to be stored on the Endpoint State. The organisationId is taken from the current requestBody. The pageNumber is taken from the current page query value.

{
...
"state": {
"extract": {
"requestBody": {
"organisationId": "organisation[0].facilityId"
},
"query": {
"pageNumber": "page"
}
}
}

In the next request to this stateful endpoint the previous requests values will be available within the full list off input data available for mapping is listed above. The two stored fields would be available as follows:

{
"requestBody": {...},
"lookupRequests": {...},
"transforms": {...},
"constants": {...},
"urlParams": {...},
"state": {
"requestBody": {
"organisationId": "<id_value"
},
"query": {
"pageNumber": "<page_number_value>"
}
},
"timestamps": {...},
}

Therefore to reference the value in a schema for example you could use the follow:

{
...
"requests": {
"lookup": [
{
"id": "organisation-details",
"config": {
"method": "get",
"url": "https://play.dhis2.org/2.35/api/organisationUnits/:org-id",
"params": {
"url": {
"org-id": {
"path": "state.requestBody.organisationId"
}
},
"query": {
"pageNumber": {
"path": "state.query.pageNumber"
}
}
}
}
}
]
}
}

This would result in a url with this format:

https://play.dhis2.org/2.35/api/organisationUnits/<id_value>?pageNumber=<page_number_value>