Digital Tooling Implementation Guide
0.0.11 - release

Digital Tooling Implementation Guide - Local Development build (v0.0.11) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions

Open API Converter

The OpenAPI Converter tool is a project developed by Te Whatu Ora Health New Zealand, which can generate an Implementation Guide package into a OpenAPI specification, for use by developers consuming FHIR APIs as well as programmatic validation tools such as API Gateways. The source code for the tool is available on GitHub.

In order to meet HNZ Api Publishing Standards, the tool requires an IG to contain a CapabilityStatement which meets the requirements of the the HnzToolingCapabilityStatement profile. This profile mandates that certain elements are provided such as license URLs, and the ability to add extensions for additional functionality such as request headers, which are not currently documented in a standard CapabilityStatement resource.

Flow

Tool Onboarding

To use the OpenAPI Converter tool, a CapabilityStatement that is an InstanceOf the HnzToolingCapabilityStatement profile resource must be created within an IG package.

Example

Add SUSHI dependency on the Digital Tooling package:

dependencies:
  tewhatuora.digitaltooling: 0.0.11 (Use latest version in the package registry https://packages2.fhir.org/packages/tewhatuora.digitaltooling)

Example fsh to create a CapabilityStatement instance

Instance: ExampleCapabilityStatement
InstanceOf: HnzToolingCapabilityStatement
Usage: #definition

The author must provide all of the required fields which are required by the HnzToolingCapabilityStatement profile, in order to generate a valid specification which in line with the Te Whatu Ora Health New Zealand API Publishing standards (the SUSHI tool will report errors in conformance to the required CapabilityStatement profile).

For a full example, view the ExampleCapabilityStatement fsh source file, or the Example Profile page.

Tool features

The OpenAPI Converter tool takes an input of a FHIR Implementation Guide package and uses the profiled CapabilityStatement resource to form an OpenAPI v3 specification in accordance to the Health New Zealand API Publishing standards.

The OpenAPI specifications generated by this tool MUST NOT be considered a full representation of the FHIR interace, as some simplifications are made to the schema. This should be used alongside application level validation performed by a tool such as the FHIR Validator.

Implementers using this tool must annotate their API using the CapabilityStatement resources effectively to best benefit from the tool.

OpenAPI Paths and Operations

For each FHIR resource annotated, an OpenAPI path is created, based on the resource interactions listed in the CapabilityStatement

Example FSH to OpenAPI Path mapping containing all REST operations:

FSH interaction OpenAPI Path and Operation
* rest.resource[=].interaction[+].code = #read GET /Patient/{rid}
* rest.resource[=].interaction[+].code = #vread GET /Patient/{rid}/_history/{vid}
* rest.resource[=].interaction[+].code = #search-type GET /Patient
* rest.resource[=].interaction[+].code = #create POST /Patient
* rest.resource[=].interaction[+].code = #update PUT /Patient/{rid}
* rest.resource[=].interaction[+].code = #patch PATCH /Patient/{rid}
* rest.resource[=].interaction[+].code = #delete DELETE /Patient/{rid}

OpenAPI Schemas

For each FHIR resource defined, the below use cases will determine which OpenAPI schemas are generated when using this tool.

Use Case 1: no profile or supportedProfile defined:

* rest.resource[+].type = #Patient
* rest.resource[=].interaction[+].code = #read

An OpenAPI schema will be generated using the R4 Patient schema. This should be used if your API only supports the base fhir resource with no particular profiles.

Case 2: supportedProfile or profile defined

* rest.resource[+].type = #Patient
* rest.resource[=].profile = Canonical(Patient)
* rest.resource[=].supportedProfile[+] = Canonical(ExamplePatientProfile)
* rest.resource[=].interaction[+].code = #read

If supportedProfile or profile are defined, schemas will be generated for each of those profiles defined. If it is desired to generate a base for the base profile, the base FHIR canonical URL for the resource should be used, e.g. Canonical(Patient). If the base fhir profile is not supported, this should not be present for either profile or any supportedProfiles.

Once the schemas are generated, they are annotated in the OpenAPI operations based on the operation type, for example GET requests will have the schemas annotated in responses, and requests with a requestBody will have the schemas associated.

StructureDefinition conversion support

Where an OpenAPI schema is created from a StructureDefinition resource in the IG, the following functionality is supported:

  • Removes not allowed elements from the schema
  • Adds patternCoding (CodeableConcepts) as enum values
  • Adds patternBoolean as an enum value
  • Adds maxItems for array items where the StructureDefinition.max cardinality is defined
  • Adds minItems for array items where the StructureDefinition.mix cardinality is defined

FHIR Custom Operations

Where custom operations are annotated for the system or type, an OpenAPI path is created for the custom operation. Where the operation mode is query, a GET /${operation} endpoint will be created. Where the operation mode is operation, a POST /${operation} endpoint is created. The parameters defined in the custom operation will be annotated in the OpenAPI spec as either query parameters if it is a GET resource, or a FHIR Parameters resource in a requestBody for a POST resource.

Case 1: Define a system level operation

* rest.operation[+].name = "summary"
* rest.operation[=].definition = Canonical(ExampleSystemOperationDefinition)

Case 2: Define a type level operation

// Patient resource
* rest.resource[+].type = #Patient
* rest.resource[=].operation[+].name = "summary"
* rest.resource[=].operation[=].definition = Canonical(ExampleQueryOperationDefinition)
* rest.resource[=].operation[+].name = "match"
* rest.resource[=].operation[=].definition = Canonical(ExampleOperationModeOperationDefinition)

Custom headers

Where an API requires HTTP headers to be provided, these can be annotated using an extension on the HnzToolingCapabilityStatement resource. These are added to all API operations.

Example fsh:

* extension[HnzApiSpecBuilderExtension].extension[globalHeaders].extension[+].url = Canonical(HnzCustomHeadersExtension)
* extension[HnzApiSpecBuilderExtension].extension[globalHeaders].extension[=].extension[key].valueString = "Correlation-Id"
* extension[HnzApiSpecBuilderExtension].extension[globalHeaders].extension[=].extension[value].valueUri = "https://raw.githubusercontent.com/tewhatuora/schemas/main/fhir-definitions-oas/uuid-definition.json"
* extension[HnzApiSpecBuilderExtension].extension[globalHeaders].extension[=].extension[required].valueBoolean = true

For each header, a key valueString is provided for the header name, and a valueUri is provided for the header value. This must be a resolveable uri to an OpenAPI schema defining the value. The required valueBoolean defines whether or not this is listed as a required or optional header.

OpenAPI Examples

Where a schema is created using a StructureDefinition resource, if there are examples contained within the IG using this profile, the examples be added as OpenAPI examples to the OpenAPI specification.

OpenAPI Security

Where the CapabilityStatement is annotated using the R4 Capability Statement Capabilities extension and oAuth uris extension, the OpenAPI operations will be annotated with the appropriate SMART on FHIR scopes.

Case 1: System to System:

Given the below FSH, which defines SMART on FHIR system to system security

* rest.security.service = #SMART-on-FHIR
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
* rest.security.extension[=].extension[+].url = "token"
* rest.security.extension[=].extension[=].valueUri = "https://auth.example.com/oauth2/token"
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
* rest.security.extension[=].valueCode = #client-confidential-symmetric

Each standard REST operation in the specification will be annotated with the appropriate SMART on FHIR scope, for example, a POST /Patient endpoint would be annotated with the system/Patient.c scope, and an OpenAPI security scheme will be generated.

Case 2: User and Patient:

* rest.security.service = #SMART-on-FHIR
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
* rest.security.extension[=].extension[+].url = "token"
* rest.security.extension[=].extension[=].valueUri = "https://auth.example.com/oauth2/token"
* rest.security.extension[=].extension[+].url = "authorize"
* rest.security.extension[=].extension[=].valueUri = "https://auth.example.com/oauth2/authorize"
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
* rest.security.extension[=].valueCode = #permission-user
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities"
* rest.security.extension[=].valueCode = #permission-patient

Each standard REST operation in the specification will be annotated with the appropriate SMART on FHIR scope, for example, a POST /Patient endpoint would be annotated with the user/Patient.c scope and patient/Patient.c, and an OpenAPI security scheme will be generated.

Case 3: Standard oAuth

* rest.security.service = #oAuth
* rest.security.extension[+].url = "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
* rest.security.extension[=].extension[+].url = "token"
* rest.security.extension[=].extension[=].valueUri = "https://auth.example.com/oauth2/token"

An OpenAPI security scheme will be generated.