JQ Transformers

 

A JQ transformer maps one transaction log event to a Communication resource. Each CMS API has its own transformer, and all transformers share the wrap-bundle.jq file that builds the transaction Bundle.

Provided Transformers

 

Smile CDR includes the transformers for the CMS APIs, the shared wrap-bundle.jq transformer, and the Spring beans that some routes use. They are on the classpath of every Smile CDR installation, with no additional files to install. The transformers are available under txlogmap/, for example:

Classpath Resource Purpose
txlogmap/wrap-bundle.jq Builds the transaction Bundle for every CMS API. See Bundle Transformer.
txlogmap/patient-access/fhir-request/transformer.jq Maps a Patient Access API request. See Record Usage in the Trace Repository.
txlogmap/patient-access/handle-error/transformer.jq The Patient Access error transformer.

Reference a provided transformer with a classpath: resource in the route, for example <jq>resource:classpath:/txlogmap/wrap-bundle.jq</jq>. To change a provided transformer, copy it to a different path under smilecdr/classes and reference the copy, so that it is not confused with the provided file. The rest of this page describes how transformers work, for writing your own.

Transformer Input and Output

 

The input of a transformer is a transaction log event, in the format shown in Example Transaction Log JSON. The event properties are under payload, and its steps are in payload.events.

The output must be a JSON object with two properties:

  • communication – The Communication resource for the event. Follow the shared conventions for its tag, identifier, and category.
  • payload – The original payload of the event, unchanged. wrap-bundle.jq reads the event steps from it.

The following transformer shows the minimum output:

((.payload?.events? // []) | map(select(.type == "ENDPOINT_RECEIVE")) | .[0]) as $requestEvent |
((.payload?.events? // []) | map(select(.type == "ENDPOINT_REPLY")) | .[0]) as $replyEvent |
{
  "communication": {
    resourceType: "Communication",
    meta: {
      tag: [{
        system: "https://smiledigitalhealth.com/communication/category",
        code: "my-api",
        display: "My API"
      }]
    },
    status: (if .payload?.outcome? == "FAIL" then "not-done" else "completed" end),
    received: ($requestEvent | .initialTimestamp?),
    sent: ($replyEvent | .initialTimestamp?),
    category: [{
      coding: [
        {
          system: "https://smiledigitalhealth.com/communication/event-type",
          code: (.payload?.type? // "N/A")
        },
        {
          system: "https://smiledigitalhealth.com/communication/event-sub-type",
          code: (.payload?.subType? // "N/A")
        }
      ]
    }],
    identifier: [{
      system: "https://smiledigitalhealth.com/communication/message-key",
      value: (.payload?.transactionGuid? // "N/A")
    }]
  },
  "payload": .payload
}

Keep the following in mind when writing a transformer:

  • The body of each event step is a string. Use fromjson to read a JSON request or response body, for example (.payload.events[0].body | fromjson). Bodies are only present when the Transaction Log Broker module publishes them, so guard against a missing body with //.
  • Use the ? operator and // alternative on optional properties, so that a missing property produces null or a default value instead of an error.
  • Use property("name") to read an exchange property set earlier in the route, for example by a functions script or a Spring bean.
  • An error raised by the transformer is handled by the error transformer.

Bundle Transformer (wrap-bundle.jq)

 

The wrap-bundle.jq transformer runs after the API-specific transformer. It builds a transaction Bundle that creates the Communication resource and one Binary resource for each event step. Each Binary resource holds the step's body, and each step appears as an attachment in Communication.payload. The urn:uuid: placeholder in each attachment URL is replaced with the ID of the created Binary resource when the trace repository processes the transaction.

Each attachment's title is the step's type, for example ENDPOINT_RECEIVE, and its contentType is derived from the step's bodyType, defaulting to application/json. A step without a body produces a Binary resource that holds an empty JSON object.

Smile CDR provides this transformer as classpath:/txlogmap/wrap-bundle.jq. Use it unchanged after your own transformers, as in the routes above: it reads only the communication and payload properties that every transformer returns.

Error Transformer

 

The route's doCatch block runs an error transformer when the API-specific transformer fails. Smile CDR provides error transformers for its transformers, such as txlogmap/patient-access/handle-error/transformer.jq; write your own for a transformer of your own. The error transformer must not depend on the content of the event beyond its transactionGuid, so that it succeeds when the API-specific transformer could not read the event. It reads the error message from the errorMessage exchange property, and tags the Communication with the error code:

{
  "communication": {
    resourceType: "Communication",
    meta: {
      tag: [
        {
          system: "https://smiledigitalhealth.com/communication/category",
          code: "my-api",
          display: "My API"
        },
        {
          system: "https://smiledigitalhealth.com/communication/category",
          code: "error",
          display: "Error not able to parse payload"
        }
      ]
    },
    status: "unknown",
    note: [{
      text: property("errorMessage")
    }],
    identifier: [
      {
        system: "https://smiledigitalhealth.com/communication/message-key",
        value: (.payload?.transactionGuid? // "N/A")
      }
    ]
  },
  "payload": .payload
}

Use the same API tag as the API-specific transformer, so that an API's errors can be searched together with its other traces. For example, the following search returns the errors of one API:

GET https://example.com/trace-fhir/Communication?_tag=https://smiledigitalhealth.com/communication/category|my-api&_tag=https://smiledigitalhealth.com/communication/category|error

Events the route cannot read at all, such as messages that are not JSON, are logged as Error in JQ transform but not traced.