Skip to main content

Profiling & Validation

Haste Health can validate resources against FHIR profilesStructureDefinition 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: