fix(validator): type Element.id-derived ids as string, not id (#424) - #465
Open
mauripunzueta wants to merge 3 commits into
Open
fix(validator): type Element.id-derived ids as string, not id (#424)#465mauripunzueta wants to merge 3 commits into
mauripunzueta wants to merge 3 commits into
Conversation
The R4B and R5 schema packs typed `ElementDefinition.id` (and every other
`Element.id`-derived id) as the FHIR primitive `id`, whose regex
`[A-Za-z0-9\-\.]{1,64}` rejects element ids containing `[x]` (choice
elements), `:` (named slices), or exceeding 64 characters — so any
StructureDefinition carrying a choice element or a named slice failed
validation, including StructureDefinitions the FHIR spec itself publishes.
Root cause is in the converter, not the packs: `EdType::effective_code()`
returns the `structuredefinition-fhir-type` extension's `valueUrl` for a
FHIRPath `System.*` code. HL7 populates that hint inconsistently — R4B
stamps `id` on every `.id`, R5 on `ElementDefinition.id`, while R4/R6 say
`string`. The authoritative type is the base element: FHIR defines
`Element.id` as `string` and `Resource.id` as the constrained `id`, and
HL7's own `fhir.schema.json` types every element id as `string` and every
resource id as `id` in all four versions.
The converter now keys the value-domain type on `base.path`: an element
deriving from `Element.id` is `string`, overriding a spurious `id`
extension. This fixes the whole class (`ElementDefinition.id`, `Element.id`,
every datatype `.id`, and the SOF `ViewDefinition` backbone ids) without
relaxing genuine resource-id validation (`Resource.id` still resolves to
`id`) and without disturbing `Extension.url` (still `uri`). Packs
regenerated: R4B/R5 element ids flip `id`→`string`; resource ids and
`Extension.url` are unchanged.
Backend-agnostic: validation and the embedded packs are shared across all
storage backends; no per-backend code paths are touched.
Verified: converter unit tests pin Element.id→string / Resource.id→id /
Extension.url→uri; an R4B/R5 end-to-end test confirms a StructureDefinition
whose element ids carry `[x]` and `:` no longer trips a primitive-value
error.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The codecov patch check flagged the tolerant multi-type arm in apply_element_content as uncovered — it had no test at all, and the #424 change touched it. Add a converter test for a non-[x] element carrying more than one type: the converter warns and uses the first type, which also exercises the value_type_override fall-through on that arm.
The pack_smoke suite — including this PR's end-to-end #424 regression — was `#[ignore]`d, and nothing in CI passes `--ignored`. So the tests that actually prove the fix against the real embedded packs never executed: not in `Test Rust`, not in the coverage job. The 100% patch coverage on this PR came entirely from the converter unit tests. The stated reason for the ignore ("whole-pack parse") does not hold: all four packs load and validate in 0.39s in a debug build. Un-ignore them so they run in the default `cargo test`, which puts the R4 pack path into the coverage measurement and the R4B/R5 #424 regression into `Test Rust` (`--workspace --all-features`). `structural_validation_latency_smoke` stays ignored — it asserts on wall-clock time, which is not a sound gate on a shared runner.
mauripunzueta
marked this pull request as ready for review
July 31, 2026 15:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #424.
Problem
The R4B and R5 schema packs typed
ElementDefinition.id(and everyElement.id-derived id) as the FHIR primitiveid, whose regex[A-Za-z0-9\-\.]{1,64}rejects element ids that contain[x](choiceelements),
:(named slices), or exceed 64 characters. AnyStructureDefinition carrying a choice element or a named slice therefore
failed validation — including StructureDefinitions the FHIR spec itself
publishes (e.g.
ActivityDefinition.subject[x]). R4 and R6 were alreadycorrect.
Root cause
In the converter, not the packs.
EdType::effective_code()resolves aFHIRPath
System.*code through thestructuredefinition-fhir-typeextension's
valueUrl. HL7 populates that hint inconsistently:ElementDefinition.idElement.idElement.id/Address.idElement.idPatient.id/Account.idResource.idExtension.urlExtension.urlThe authoritative type is the base element: FHIR defines
Element.idas
stringandResource.idas the constrainedidtoken, and HL7's ownfhir.schema.jsontypes every element id asstring, every resource id asid, andExtension.urlasuri, in all four versions.Fix
The converter now keys the value-domain type on
base.path: an elementderiving from
Element.idresolves tostring, overriding a spuriousidextension.
Resource.idandExtension.urlcontinue to honor theextension.
This was chosen over the issue's option 1 ("map every
System.String→string"), which would have relaxed resource-id validation(
Patient.idallowing spaces / >64 chars) and demotedExtension.urltostring— both contradictingfhir.schema.json. The base-driven rulefixes the whole class without those regressions.
Effect on the regenerated packs
Element.id-derived id flipsid→string(60 and73 elements). Resource ids stay
id;Extension.urlstaysuri.string; the only change is 5ViewDefinitionbackbone.idfields (select.id,where.id,constant.id,select.column.id,select.column.tag.id), which areElement.id-derived and carried the same spuriousidextension. Theseare genuine instances of the same bug;
ViewDefinition.id(the resourceid) is untouched.
Scope / DB
Entirely within
helios-fhir-validator(converter + embedded packs).Validation is backend-agnostic and the packs are shared across all storage
backends, so there are no per-backend / no SQL code paths to change.
Tests
Element.id→string,Resource.id→id,Extension.url→uri, and that the override isindependent of the (inconsistent) extension value.
pack_smoke,#[ignore]d like its siblings)validates a StructureDefinition whose element ids carry
[x]and:andasserts no primitive-value error on any
.idpath. Pre-fix thisproduced two such errors per version; post-fix, zero.
helios-fhir-validatorsuite green;fmtandclippyclean.Notes
string/markdownregex rejects non-breaking spaces on R4 and R4B (Unicode-aware\S) #425 (nbsp vs Unicode\Sin thestringregex) — thestring regex is deliberately untouched.
primitive-valuecluster attributable to this bug drops out; that PR's baselines will need
regenerating once both land.