Article
· Jan 31 10m read

The New FHIR Server Profile-based Validation

New in version 2023.3 (of InterSystems IRIS for Health) is a capability to perform FHIR profile-based validation.

 (*)

In this article I'll provide a basic overview of this capability.

If FHIR is important to you, you should definitely try out this new feature, so read on.

 

Background

The FHIR standard defines an operation called $validate. This operation is intended to provide an API to validate Resources.

To understand more about FHIR Validation in general you can see this FHIR documentation

You can also see my session from Global Summit 2023 - "Performing Advanced FHIR Validation", where in the first part I provide information about various types of validations.

Part of this validation is to validate against specific profiles. You can see here about Profiling.

To illustrate, as a simple example, the base FHIR definition for the Patient Resource, defines the cardinality of identifier as '0..*', meaning that a Patient might not have any identifier (zero) and still be valid. But the US Core Patient Profile defines a cardinality of '1..*', meaning that no identifier for a Patient would not be valid.

Other examples, following the US Core Patient sample above, would be using extensions, such as race or birthsex.

If you'd like to learn more about FHIR Profiling you can have a look at a session from our Global Summit 2022 which @Patrick Jamieson presented - "Using FHIR Shorthand", where Pat explains about using FSH (FHIR Shorthand), but starts off by covering the general topic of Profiling.

In previous versions, our FHIR Server did not support this type of validation - profile-based validation, but from the latest version (2023.3) this is supported.

 

Usage

Our documentation includes a section about how to call $validate for profile-based validation.

There are two basic ways to call the $validate operation.

Profile in Query URL

One is sending a POST with the Resource in the Request Body and the profile as a URL parameter.

For example, in Postman:

Or using curl (note the encoding of the slashes in the profile URL parameter's value; Postman takes care of that for you):

 curl --location 'http://fhirserver/endpoint/Patient/$validate?profile=http%3A%2F%2Fhl7.org%2Ffhir%2Fus%2Fcore%2FStructureDefinition%2Fus-core-patient%7C3.1.0' --header 'Content-Type: application/fhir+json' --header 'Accept: application/fhir+json' --header 'Authorization: Basic U3VwZXJVc2VyOnN5cw==' --data "@data.json"

While data.json referred to above includes this valid US Core Patient for example -

 
Patient Resource JSON

The response of this operation is an OperationOutcome Resource.

If the Resource is valid (as per above) you will expect to get this kind of response -

{
    "resourceType": "OperationOutcome",
    "issue": [
        {
            "severity": "information",
            "code": "informational",
            "details": {
                "text": "All OK"
            }
        }
    ]
}

But if for example I will omit the identifiers from the Resource above, I will get this OperationOutcome:

{
    "resourceType": "OperationOutcome",
    "issue": [
        {
            "severity": "error",
            "code": "invariant",
            "details": {
                "text": "generated-us-core-patient-1: Constraint violation: identifier.count() >= 1 and identifier.all(system.exists() and value.exists())"
            },
            "diagnostics": "Caused by: [[expression: identifier.count() >= 1, result: false, location: Patient]]",
            "expression": [
                "Patient"
            ]
        }
    ]
}

Profile in the Query Body

The other way of sending the data to $validate is sending a POST with the Resource inside a Parameters array, with profile and other options.

In Postman this will look now like this:

With curl:

curl --location 'http://fhirserver/endpoint/Patient/$validate' --header 'Content-Type: application/fhir+json' --header 'Accept: application/fhir+json' --header 'Authorization: Basic U3VwZXJVc2VyOnN5cw==' --data "@data.json"

Note the URL does not include a profile, but now the body payload (or data.json in the example above) looks like this:

{
    "resourceType": "Parameters",
    "parameter": [
        {
            "name": "mode",
            "valueString": "profile"
        },
        {
            "name": "profile",
            "valueUri": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|3.1.0"
        },
        {
            "name": "resource",
            "resource": {
                "resourceType": "Patient"
            }
        }
    ]
}

I excluded the actual Patient Resource, as it is the same as in the previous examples.

But what is different here is the mode parameter element, and the profile one, and resource comes within it's own parameter element simply named 'resource'.

See the docs referenced above for more options for mode, including where an ID can be included in the URL (to validate for example regarding it's update or deletion).

For your convenience I created a simple Open Exchange package that includes a Postman Collection with sample requests per above.

Possible Errors

If you omit a version for the profile in the format of <profile URI>|<version number> you will get an <HSFHIRErr>ProfileVersionRequired error.

You would get a 400 status code and this kind of OperationOutcome -

{
    "resourceType": "OperationOutcome",
    "issue": [
        {
            "severity": "error",
            "code": "invalid",
            "diagnostics": "<HSFHIRErr>ProfileVersionRequired",
            "details": {
                "text": "Profile URI 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient' does not include a version number.  Version number is required for profile validation."
            }
        }
    ]
}

If you provide a version number that does not exist on the FHIR Endpoint you will get a not-supported error (this time the HTTP status will be 200).

Your OperationOutcome will look like this for example -

{
    "resourceType": "OperationOutcome",
    "issue": [
        {
            "severity": "error",
            "code": "not-supported",
            "details": {
                "text": "Profile 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient|15' is not supported"
            },
            "expression": [
                "Patient"
            ]
        }
    ]
}

Via Code

Note alternatively to calling the $validate operation via the standard REST API, internally, if the use-case is appropriate, you can also call a class method.

Similarly to the method that already existed HS.FHIRServer.Util.ResourceValidator:ValidateResource() (mentioned also here in the Docs) there is now also a new method ValidateAgainstProfile() which you can utilize.

 

Scope

It is important to note that currently (v2023.3) this type of validation (profile-based validation) happens only as part of the $validate operation, but not when you create or update Resources. There more "basic" validation happens. So if you want you can run your Resources through the more "advanced" profile-based validation before they get POST'ed or PUT.

Other options might be available in future versions.

 

Setup Notes

In general the good news is that the FHIR Server will take care of most of the Profile Validator setup for you.

What you do need to make sure is that you have a supported Java 11 JDK (currently that's the Oracle one or OpenJDK's).

You can find more details in the Configuring the Profile Validation Server documentation.

Basically what happens behind the scenes is that we make sure there is an External Language (Java) Server running, in order to execute the Validator JAR (located in the installation folder, under dev/fhir/java; By the way if you peek in the logs folder there and you see some warnings, such as:

CodeSystem-account-status.json : Expected: JsonArray but found: OBJECT for element: identifier

don't get concerned, that's fine. The Validator loads many profiles by default and some of them have some formatting errors).

So, if you look at your External Language Servers list, you should see you have something like this:

Note when the Validator needs to validate against a profile, for the first time, it needs to load them, so to improve performance you can call the HS.FHIRServer.Installer:InitalizeProfileValidator() method:

set status = ##class(HS.FHIRServer.Installer).InitializeProfileValidator(.error)

This is also mentioned in the above referenced documentation for configuring the Validator.

Indeed you could include this call in your instance's %ZSTART startup routine.

And this is also mentioned in the related Class Reference:

It is recommended to call this method after a restart of the Instance or External Language Server so that we don't have the performance hit of loading the profiles during the validate operation.

 

Coming Soon...

In upcoming versions we plan to provide more functionality in and around the Validator.

But, for example, even today if you wish to perform external Terminology Server based validation (such as for LOINC codes) you can use a different approach, one explained and demonstrated in my Global Summit session I mentioned above, based on my colleague @Dmitry Zasypkin's sample (available on Open Exchange). 

Special Thanks

Thank you to @Kimberly Dunn who was an invaluable source of information while examining this new feature and preparing this article.

 

(*) Thanks to Microsoft Bing's DALL-E 3 powered Image Creator who created the image above for me.

Discussion (1)1
Log in or sign up to continue