Validating data against profiles
The profile validator is distributed as a separate package, and can be found on NuGet, by searching for Firely.Fhir.Validation.<spec version>
. The main class in this package is the Validator. To construct a new instance, we can supply the constructor with several dependencies:
Required: A reference to a resource resolver (see FHIR Package Source), specifically an
IAsyncResourceResolver
. This service allows the validator to retrieve the profiles that are referenced by the profile being validated.Required: A reference to a terminology service (see Using Terminology Services), specifically a
ICodeValidationTerminologyService
. This service is needed to validate the codes used in the instance against the code systems and value sets specified in the profile.Optional, but recommended: pass in an
IExternalReferenceResolver
. This service allows the validator to retrieve the resources that are referenced by the instance being validated. If this service is not supplied, the validator will simply not try to validate the resources that these references point to.Optional: a set of additional settings, as an instance of a ValidationSettings.
Note
The validator translates the StructureDefinitions to an optimized, internal representation, which are cached in the validator. It is therefore recommended to reuse the same instance of the validator for multiple validations.
var packageServer = "https://packages.simplifier.net";
var fhirRelease = FhirRelease.STU3;
var packageResolver = FhirPackageSource.CreateCorePackageSource(ModelInfo.ModelInspector, fhirRelease, packageServerUrl);
var resourceResolver = new CachedResolver(packageResolver);
var terminologyService = new LocalTerminologyService(resourceResolver);
var validator = new Validator(resourceResolver, terminologyService);
var testOrganization = new Organization { };
var profile = Canonical.ForCoreType("Organization").ToString();
var result = validator.Validate(testOrganization, profile);
The above code initializes a package resolver, which is configured to retrieve the basic core FHIR STU3 packages. It also creates a terminology service, which is configured to use the same resource resolver to retrieve the code systems and value sets that are referenced by the profiles. Finally, it creates a validator, and runs a validation against a new instance of the Organization resource, using the standard Organization profile.
In practice, you would configure additional resolvers (using a MultiResolver
), which could combine the core packages with profiles found on a shared drive storage, a database etcetera.
Validation results
The validator returns a FHIR OperationOutcome
resource. It has an additional Success
property that can be checked to see if there are any important issues. The Fatals
, Errors
and Warnings
contain the number pf issues that were found during validation for each category.
var result = validator.Validate(p);
result.Issue.SelectMany(i => i.Details.Coding).Should().Contain(c => c.Code == Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code.ToString());
result.Success.Should().BeFalse();
External references
FHIR instances can contain references to other resources, which can be used to validate the instance. For example, a Patient resource can contain a reference to a Practitioner. The validator can be configured with an IExternalReferenceResolver
to retrieve these resources. If this service is not supplied, the validator will simply not try to validate the resources that these references point to.
The interface looks like this:
/// <summary>
/// A service that can resolve external references to other resources.
/// </summary>
public interface IExternalReferenceResolver
{
/// <summary>
/// Resolves the reference to a resource. The returned object must be either a <see cref="Resource"/> or <see cref="ElementNode"/>
/// </summary>
/// <returns>The resource or element node, or null if the reference could not be resolved.</returns>
Task<object?> ResolveAsync(string reference);
}
When implementing this interface, you can return either a Resource
or an ElementNode
, depending on whether you are working with POCO’s or ITypedElement
-based models. Return null
if the reference cannot be resolved.
Selecting profiles to validate against
In the first Validate()
example above, we passed an explicit profile url to validate against. This is not always necessary. If you leave out the profile url, the validator will try to find a profile url in the Meta
of the instance. If it finds one, it will validate against that profile. If it does not find one, it will validate against the “default” core profile for the resource type. You would normally only pass in an explicit profile url if you want to validate against a specific profile, e.g. one for US Core or other national profiles.
The behaviour of following the profiles in Meta can be changed by setting the MetaProfileSelector
property.
Other configuration operations
You can pass in an instance of the ValidationSettings
class to the constructor of the Validator
. This class contains several properties that can be used to configure the behaviour of the validator. The most important ones are:
Property |
Use |
---|---|
ConstraintBestPractices |
Determines how to deal with failures of FhirPath constraints marked as “best practice”. Default is |
SelectMetaProfiles |
Determines which profiles from a Resource’s |
FollowExtensionUrl |
Determines what do do when an extension is encountered. If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when the extension cannot be resolved and is a modififier extension. |
TypeNameMapper |
A function that maps a type name found in |
SetSkipConstraintValidation() |
Enables or disables the validation of FhirPath constraints. Default is |
Full Example
We have created a full example that shows how to use the validator using a terminology service and the FHIR core package resolvers. See this GitHub repo for more information.