Skip to main content

Profiling & Validation

Haste Health can validate resources against FHIR profiles — StructureDefinition resources that constrain a base FHIR type with tighter cardinality, fixed values, patterns, and slicing rules. This is how implementation guides like US Core define what a conformant Patient or Observation must look like, and Haste Health enforces those rules automatically at write time.

How It Works​

Validation is triggered by the meta.profile field on a resource. Whenever a resource is created or updated, the server:

  1. Reads the canonical URLs listed in meta.profile (a resource may declare at most 2 profiles).
  2. Resolves each URL to a StructureDefinition resource stored in the current project.
  3. Recursively validates the resource's data against that profile's element definitions.
  4. Rejects the write with a OperationOutcome if any element fails validation.
{
"resourceType": "Patient",
"meta": {
"profile": ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]
},
"name": [{ "family": "Doe", "given": ["John"] }],
"gender": "male"
}

If the resource has no meta.profile, this step is skipped entirely and the resource is validated only against the base FHIR R4 structure.

What Gets Validated​

For each element the profile constrains, Haste Health checks:

  • Cardinality: min/max occurrence, including per-slice cardinality.
  • Type constraints: the element's value must match one of the profile's allowed types (including references to nested profiles, which are validated recursively).
  • Fixed values (fixed[x]): the value must match exactly.
  • Patterns (pattern[x]): the value must match on the fields the pattern specifies (a partial/subset match, not exact equality).
  • Slicing: repeating elements (e.g. Patient.extension, Observation.component) are split into slices using the profile's discriminators, and each slice's cardinality and constraints are validated independently.

Slicing supports these discriminator types: value (fixed value, pattern, or a coded ValueSet binding on Coding/CodeableConcept), pattern, type, and exists.

Resolving Profiles​

Profiles are resolved by searching for a StructureDefinition with a matching url in the same tenant and project as the resource being validated (results are cached for 2 hours). This means:

  • A profile must exist as a StructureDefinition resource in your project before you can reference it in meta.profile.
  • Only profiles with derivation: "constraint" are supported — specialization-derived StructureDefinitions aren't valid targets for this check.
  • If a referenced profile can't be resolved, validation is skipped for that profile rather than failing the write — the server logs a warning and lets the request through. Don't rely on an unresolvable profile URL to reject bad data.

Using US Core​

Haste Health's profiling engine has been built and tested against the US Core implementation guide, including its cardinality, fixed-value, and slicing rules (e.g. race/ethnicity extensions, US Core Observation category slicing).

US Core StructureDefinitions aren't preloaded into new projects — you load the ones you need, the same way you'd load any other resource:

  1. Download the profiles you need from the US Core StructureDefinitions.e
  2. Load StructureDefinitions into your project:
haste-health api create StructureDefinition --file StructureDefinition-us-core-patient.json
  1. Reference the profile's canonical url in meta.profile on resources you create.

See Load Sample Data for more on bulk-loading resources via the CLI.

What We Don't Support​

The profiling engine covers the structural checks most IGs rely on, but a few things aren't implemented yet:

  • profile and null discriminators: slicing that discriminates on a nested profile or has no discriminator at all isn't supported. Validation errors out if a profile relies on one.
  • General terminology binding: ValueSet membership isn't validated for plain coded elements (code, Coding, CodeableConcept outside of slicing). The one exception is value-discriminator slicing, where a binding on a slice is used to split Coding/CodeableConcept values by their system — that's a slicing heuristic, not a real $validate-code terminology check. required/extensible/preferred binding strength is not enforced anywhere.
  • Specialization profiles: only StructureDefinitions with derivation: "constraint" can be used for validation. Base-type specializations aren't valid targets.
  • More than 2 profiles per resource: meta.profile is capped at 2 entries; a resource declaring more is rejected before validation even starts.

If a profile depends on any of the unsupported discriminator paths or types, expect a not-supported OperationOutcome issue rather than a silent pass.

Error Handling​

A failed validation returns an OperationOutcome describing every violation, with location set to a pointer into the resource:

{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "value",
"diagnostics": "Element: 'Patient.name.family' Minimum number of required values not met expected at least '1', found '0'",
"location": ["Patient.name.0.family"]
}
]
}

Related Documentation: