Lesson 45 of 51 · Validation and Testing

Validating Resources Against Profiles

FHIR Validation

Why validation matters

A profile describes what a conforming resource must look like: which elements are required, what cardinalities apply, which value sets coded fields must draw from, and what extra constraints an implementation guide layers on top of the base specification. But a profile only becomes enforceable when something checks instances against it. Validation is that check. Without it, a profile is aspirational documentation; with it, the same profile becomes a contract a system can mechanically hold every resource to 1.

This lesson closes the loop opened by the profiling material. You have seen how a StructureDefinition expresses rules and how implementation guides such as US Core assemble those rules into a coherent package. Here we examine how a validator turns those rules into pass/fail judgments, and how the results are reported so that both people and software can act on them.

What a validator checks

Validation is layered. A validator works from the most basic concerns outward to the most specific, and a failure at any layer is reported rather than silently ignored 1.

  • Structure: the resource is well-formed JSON or XML and matches the base resource definition. Every element is a known element of that resource, and elements are nested correctly. Unrecognized or misplaced elements are flagged here.
  • Cardinality: required elements are actually present, and repeating elements appear within their allowed min..max bounds. An element declared 1..1 must occur exactly once.
  • Data types and value domains: each element holds a valid value of its declared type. A date must be a syntactically valid date; a code must match the rules for that primitive.
  • Terminology bindings: coded elements carry codes that are valid members of the value set bound to that element. This is where validation ties back to the $validate-code operation from the terminology material — the validator is effectively asking a terminology service whether a code belongs to the bound value set.
  • Invariants: the FHIRPath constraints defined on the resource or profile evaluate to true. These express rules that simple cardinality and type checks cannot, such as “if element A is present, element B must also be present.”
  • Profile conformance: beyond the base resource, the instance satisfies a named profile’s added constraints. This includes how the instance handles Must Support elements and whether it conforms to any slicing the profile defines on repeating elements 1.

How validation is invoked

There are two common ways to run these checks. The first is the official FHIR validator tooling, a command-line and library implementation that loads the relevant profiles and implementation guides and validates instances offline. This is well suited to build pipelines and pre-submission checks.

The second is the $validate operation exposed by a FHIR server. A client POSTs a resource to the operation endpoint and may optionally name the profile to validate against. The server then runs the same layered checks and returns its verdict. Naming the profile is important: it tells the validator to apply not just the base resource rules but the specific constraints of the profile you intend to conform to.

Reading the result: OperationOutcome

Validation does not simply return “valid” or “invalid.” It returns an OperationOutcome resource — a structured list of issues, each carrying a severity of error, warning, or information, along with diagnostics and often a location pointing at the offending element 1. Because the result is itself a resource, problems are machine-readable: a pipeline can fail a build on any error, surface warning entries for review, and log information entries without blocking.

The example below sketches a $validate call and a returned OperationOutcome reporting a single error.

POST [base]/Patient/$validate?profile=http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient
Content-Type: application/fhir+json

{ "resourceType": "Patient", "name": [ { "family": "Lee" } ] }

--- response ---

{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "required",
      "diagnostics": "Patient.identifier: minimum required = 1, but only found 0",
      "location": [ "Patient.identifier" ]
    }
  ]
}

The practical payoff

The discipline that pays off is validating against the target profile or implementation guide — for example US Core — before a resource reaches production. Doing so catches the failures that are otherwise discovered late and expensively: missing Must Support elements, codes that are not members of a bound value set, and cardinality violations such as the missing required identifier above. Caught at the validator, these are quick edits. Caught at an interface boundary between two production systems, the same problems become rejected messages, failed exchanges, and incident tickets. Validation moves that detection as early as possible, which is exactly where it is cheapest to fix.

References

  1. HL7 FHIR Release 4 (R4), v4.0.1. HL7 International. 2019. verified Cited at: validation.html; profiling.html; operationoutcome.html.