From cf94b0d83151bad8158d40e55bf2b93e4e3d41a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 18:46:38 +0000 Subject: [PATCH] Match Base as an extension element context explicitly The _datatype extension in the FHIR extensions pack declares its context as {element, Base}. This already matched through the type-hierarchy check, but now short-circuits like Element, mirroring the Java validator (7.0.2). Adds a test placing _datatype on a resource, primitive, datatype, backbone element and the value of a cross-version extension. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ST5JwEcxHr8vYcyDk6zwQk --- .../Impl/ExtensionContextValidator.cs | 4 +- .../Impl/ExtensionContextValidatorTests.cs | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index 768db829..a1c9a757 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -115,7 +115,9 @@ private bool validateContext(PocoNode input, TypedContext context, ValidationSet private static bool validateElementContext(string contextExpression, PocoNode instance, ValidationSettings vc) { - if (contextExpression == "Element") return true; + // Base is the root of the FHIR type hierarchy (used by e.g. the _datatype extension), so like Element + // it matches anything + if (contextExpression is "Element" or "Base") return true; // Element ids can be profile-qualified ([url]#[elementid]). We cannot verify conformance against // the profile here, so we match on the element id part only diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs index e7c8c578..83ce25c7 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using Hl7.Fhir.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Linq; using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; @@ -220,6 +221,48 @@ public void ExtensionContext_OnBackboneElement_MatchesAbstractType(string expres result.IsSuccessful.Should().Be(expected); } + // The _datatype extension from the FHIR extensions pack declares its context as {element, Base}. Base is the + // root of the FHIR type hierarchy, so the extension may appear on any element, including resources and the + // value of a cross-version extension (its main use). + // https://build.fhir.org/ig/HL7/fhir-extensions/StructureDefinition-datatype.html + [DataTestMethod] + [DataRow("")] // the resource itself + [DataRow("active")] + [DataRow("name")] + [DataRow("contact")] + [DataRow("extension.value")] + public void ExtensionContext_BaseContextOfDatatypeExtension_MatchesAnyElement(string path) + { + var crossVersionValue = new FhirString("9223372036854775807"); + var patient = new Patient + { + ActiveElement = new FhirBoolean(true), + Name = [new HumanName { Family = "Doe" }], + Contact = [new Patient.ContactComponent { Gender = AdministrativeGender.Other }], + Extension = [new Extension("http://example.org/extensions/cross-version", crossVersionValue)] + }; + + IExtendable host = path switch + { + "" => patient, + "active" => patient.ActiveElement, + "name" => patient.Name[0], + "contact" => patient.Contact[0], + "extension.value" => crossVersionValue, + _ => throw new ArgumentOutOfRangeException(nameof(path)) + }; + host.AddExtension("http://hl7.org/fhir/StructureDefinition/_datatype", new FhirString("integer64")); + + var ctxValidator = new ExtensionContextValidator([new(ExtensionContextValidator.ContextType.ELEMENT, "Base")], []); + var validator = new ChildrenValidator([("extension", ctxValidator)]) { AllowAdditionalChildren = true }; + foreach (var name in path.Split('.', StringSplitOptions.RemoveEmptyEntries).Reverse()) + validator = new ChildrenValidator([(name, validator)]) { AllowAdditionalChildren = true }; + + var result = validator.Validate(patient.ToPocoNode(), new ValidationSettings(), new ValidationState()); + + result.IsSuccessful.Should().BeTrue(); + } + // Issues 540/481: Parameters.parameter.part is defined via contentReference to Parameters.parameter, // so its TypeName is "Parameters.parameter". A context of "Parameters.parameter" must therefore match // an extension placed on a .part element, even though the literal instance path is "parameter.part".