A lightweight, Clojure-native, data-driven FHIR validator.
Based on: https://hl7.org/fhir/validation.html
fhemas is built around the idea of validating FHIR resources directly, without depending on the Java FHIR ecosystem. Given the FHIR resources required for validation (the official FHIR package — StructureDefinitions, ValueSets, etc.), the engine validates any target resource against them.
The library stays data-driven: a custom resource called ValidatorDefinition tells the engine exactly what to extract from a StructureDefinition and which functions to apply to process each field. The engine never guesses — all extraction and compilation behavior is explicit, external configuration, not hardcoded per FHIR version.
fhemas is currently in early active development. See the GitHub Projects board for current progress and planned work.
- Structure
- Cardinality
- Value Domains
- Coding/CodeableConcept bindings
- Invariants
- Profiles
- Questionnaires
- Business Rules (explicitly defined by the spec as outside the specification itself)
-
Questionnaire / QuestionnaireResponse: out of scope for v1. Self- contained sub-validator with its own rules, independent from StructureDefinition-based validation. Not discarded — revisited later.
-
Business Rules (reference resolution, duplicate checks, authorization, etc.): the spec itself lists these as rules "made outside the specification". Treated as an optional, decoupled layer on top of the core engine. Behavior (hard error / warning / skip) is configurable by the implementer.
Per the FHIR spec (validation.html, 7.6.1): full validation, especially terminology, can be computationally expensive, and production use may need to tolerate imperfect data that development environments would reject. Following Postel's law (conservative in what you send, liberal in what you accept), fhemas does not hardcode a single strictness level.
Validation strictness is configurable per pipeline aspect (e.g. Cardinality, Bindings, Invariants), not as a single global on/off switch — a consumer may need Structure to fail hard (security: malicious narrative content) while treating Bindings as a warning (cost: avoids expensive terminology lookups) in the same run.
This configuration is passed via a context map at validation time (not a stateful object) — consistent with fhemas staying data-driven throughout. Sensible defaults are provided so a consumer isn't forced to configure every aspect explicitly; defaults can be overridden granularly.
Exact default values and the full shape of this configuration are still being designed — see the roadmap.
The spec lists validation aspects but does not prescribe an order. The order below was derived by asking, for each aspect: "what must already be resolved for this aspect to be meaningful?" This pipeline order is a fixed responsibility of the engine — it is not something a SchemaProfile declares or a consumer can reconfigure.
- Hard dependency: aspect B cannot be checked correctly without aspect A having happened first. Reordering breaks correctness.
- Optimization: aspects are independent of each other, but one runs first because it's cheaper to compute, enabling fail-fast.
| Step | Aspect | Ordering reason |
|---|---|---|
| 0 | Resolve applicable Profile/StructureDefinition | Hard dependency — prerequisite for everything else. |
| 1 | Structure | Hard dependency on step 0. |
| 2 | Cardinality | Hard dependency on step 0. Runs before Value Domains as an optimization (cheaper, fails fast). |
| 3 | Value Domains | Hard dependency on step 0. Logically independent from Cardinality. |
| 4 | Invariants | Hard dependency on step 3. Runs before Bindings as an optimization: FHIRPath evaluation is local computation, while Bindings may require expanding a ValueSet or querying an external terminology server. |
| 5 | Bindings (Coding/CodeableConcept) | Hard dependency on step 3. Logically independent from Invariants — ordering vs. step 4 is a cost-based optimization, not a correctness requirement. |
If ValueSet expansion is fully local and cached, the optimization reason for ordering Invariants before Bindings no longer applies, and both could run in any order — the hard dependencies on step 3 still hold regardless.
Deferred to later phases (not part of the core pipeline above):
- Profiles (validating rules added by a specific profile, distinct from resolving which profile applies in step 0): layered on top of steps 1–5.
- Questionnaires: separate sub-validator, out of scope for v1.
- Business Rules (including reference resolution): optional layer, see Scope decisions above.
- Element/Extension mutual recursion:
Element.extensionis of typeExtension, andExtensionitself extendsElement(it also has an.extensionfield). This is structural self-reference by design in the base FHIR model — almost every element carries anextensionfield. Since fhemas resolves schemas by looking them up by URL against finite input data (rather than generating compiled code ahead of time), this should not cause the build-time infinite recursion that code-generation approaches need to work around (e.g. via memoized lazy references). Still, this needs to be handled explicitly in the resolution/lookup mechanism — either through reference-by-lookup (not reconstruction) at each recursion level, or an explicit depth guard — to avoid stack overflows on deeply nested extensions. Not yet designed; noted here so it isn't missed when implementing profile/type resolution.