From 86acd681f8c30fcbc8d11726c17bd7a68c087c85 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Thu, 12 Sep 2024 12:53:32 +0200 Subject: [PATCH 01/25] wip --- .../.idea/projectSettingsUpdater.xml | 2 +- .../CommonExtensionContextComponent.cs | 80 ++++++++++ ...ir.Validation.Compilation.Shared.projitems | 2 + .../SchemaBuilder.cs | 2 +- .../SchemaBuilders/ExtensionContextBuilder.cs | 22 +++ .../SchemaBuilders/StandardBuilders.cs | 3 +- .../Impl/ExtensionContextValidator.cs | 149 ++++++++++++++++++ .../Impl/FhirEle1Validator.cs | 8 +- .../Impl/FhirExt1Validator.cs | 4 +- .../Impl/FhirPathValidator.cs | 8 +- .../Impl/FhirTxt1Validator.cs | 10 +- .../Impl/FhirTxt2Validator.cs | 4 +- .../Impl/InvariantValidator.cs | 10 +- .../Impl/SliceValidator.cs | 31 +++- .../PublicAPI.Unshipped.txt | 13 ++ .../SchemaSnaps/PatternSlice.json | 2 + .../SchemaSnaps/SecondaryTypeRefSlice.json | 3 + .../SchemaSnaps/PatternSlice.json | 2 + .../SchemaSnaps/SecondaryTypeRefSlice.json | 3 + .../SchemaSnaps/PatternSlice.json | 2 + .../SchemaSnaps/SecondaryTypeRefSlice.json | 3 + .../SchemaSnaps/PatternSlice.json | 2 + .../SchemaSnaps/SecondaryTypeRefSlice.json | 3 + .../BasicSchemaBuilderTests.cs | 4 +- .../FhirTestCases | 2 +- .../Impl/FhirUriValidatorTests.cs | 1 - 26 files changed, 344 insertions(+), 31 deletions(-) create mode 100644 src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs create mode 100644 src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs create mode 100644 src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs diff --git a/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml b/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml index 4bb9f4d2..86cc6c63 100644 --- a/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs b/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs new file mode 100644 index 00000000..aa9bbc49 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs @@ -0,0 +1,80 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Navigation; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +#pragma warning disable CS0618 // Type or member is obsolete +namespace Firely.Fhir.Validation.Compilation +{ + internal class CommonExtensionContextComponent + { + internal CommonExtensionContextComponent(IEnumerable<(ExtensionContextValidator.ContextType?, string)> contexts, + IEnumerable invariants) // internal for testing purposes + { + Contexts = contexts; + Invariants = invariants; + } + + internal IEnumerable<(ExtensionContextValidator.ContextType?, string)> Contexts { get; private set; } + + internal IEnumerable Invariants { get; private set; } + +#if STU3 + public static bool TryCreate(ElementDefinitionNavigator nav, [NotNullWhen(true)] out CommonExtensionContextComponent? result) + { + var strDef = nav.StructureDefinition; + if (strDef.ContextType is null && !strDef.Context.Any() && !strDef.ContextInvariant.Any()) // if nothing is set, we don't need to validate + { + result = null; + return false; + } + + ExtensionContextValidator.ContextType? contextType = strDef.ContextType switch + { + StructureDefinition.ExtensionContext.Resource => ExtensionContextValidator.ContextType.RESOURCE, + StructureDefinition.ExtensionContext.Datatype => ExtensionContextValidator.ContextType.DATATYPE, + StructureDefinition.ExtensionContext.Extension => ExtensionContextValidator.ContextType.EXTENSION, + null => null, + _ => throw new InvalidOperationException($"Unknown context type {strDef.ContextType.Value}") + }; + + var contexts = strDef.Context.Select(c => (contextType, c)); + + var invariants = strDef.ContextInvariant; + + result = new CommonExtensionContextComponent(contexts, invariants); + return true; + } +#else + public static bool TryCreate(ElementDefinitionNavigator def, [NotNullWhen(true)] out CommonExtensionContextComponent? result) + { + var strDef = def.StructureDefinition; + if (strDef.Context.Count == 0 && !strDef.ContextInvariant.Any()) // if nothing is set, we don't need to validate + { + result = null; + return false; + } + + IEnumerable<(ExtensionContextValidator.ContextType?, string)> contexts = strDef.Context.Select(c => + ( + c.Type switch + { + StructureDefinition.ExtensionContextType.Fhirpath => ExtensionContextValidator.ContextType.FHIRPATH, + StructureDefinition.ExtensionContextType.Element => ExtensionContextValidator.ContextType.ELEMENT, + StructureDefinition.ExtensionContextType.Extension => ExtensionContextValidator.ContextType.EXTENSION, + _ => null + }, + c.Expression + ) + ); + + var invariants = strDef.ContextInvariant; + + result = new CommonExtensionContextComponent(contexts, invariants); + return true; + } +#endif + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/Firely.Fhir.Validation.Compilation.Shared.projitems b/src/Firely.Fhir.Validation.Compilation.Shared/Firely.Fhir.Validation.Compilation.Shared.projitems index ce12feb0..f836e746 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/Firely.Fhir.Validation.Compilation.Shared.projitems +++ b/src/Firely.Fhir.Validation.Compilation.Shared/Firely.Fhir.Validation.Compilation.Shared.projitems @@ -9,12 +9,14 @@ Firely.Fhir.Validation.Compilation.Shared + + diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index 2e837848..20e52168 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -352,7 +352,7 @@ internal IAssertion CreateSliceValidator(ElementDefinitionNavigator root) // default). IAssertion caseConstraints = discriminatorless ? ResultAssertion.SUCCESS : convertElementToSchema(schemaId, root); - sliceList.Add(new SliceValidator.SliceCase(sliceName ?? root.Current.ElementId, condition, caseConstraints)); + sliceList.Add(new SliceValidator.SliceCase(root.Current.ElementId, sliceName ?? root.Current.ElementId, condition, caseConstraints)); } } diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs new file mode 100644 index 00000000..2b217bab --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs @@ -0,0 +1,22 @@ +using Hl7.Fhir.Language.Debugging; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Navigation; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation.Compilation; + +/// +/// +/// +#pragma warning disable CS0618 // Type or member is obsolete +internal class ExtensionContextBuilder : ISchemaBuilder +{ + public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode) + { + if (nav is { Path: "StructureDefinition", StructureDefinition.Type: "Extension" } && CommonExtensionContextComponent.TryCreate(nav, out var trc)) + { + yield return new ExtensionContextValidator(trc.Contexts, trc.Invariants); + } + } +} diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/StandardBuilders.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/StandardBuilders.cs index 0075fe65..1dc69951 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/StandardBuilders.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/StandardBuilders.cs @@ -37,7 +37,8 @@ public class StandardBuilders(IAsyncResourceResolver source) : ISchemaBuilder new TypeReferenceBuilder(source), new CanonicalBuilder(), new FhirStringBuilder(), - new FhirUriBuilder() + new FhirUriBuilder(), + new ExtensionContextBuilder() }; /// diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs new file mode 100644 index 00000000..b22676cd --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -0,0 +1,149 @@ +using Hl7.Fhir.FhirPath; +using Hl7.Fhir.Support; +using Hl7.FhirPath; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation; + +/// +/// An assertion which validates the context in which the extension is used against the expected context. +/// +[DataContract] +[EditorBrowsable(EditorBrowsableState.Never)] +#if NET8_0_OR_GREATER +[System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] +#else +[System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] +#endif +public class ExtensionContextValidator : BasicValidator +{ + /// + /// Creates a new ExtensionContextValidator with the given allowed contexts and invariants. + /// + /// + /// + public ExtensionContextValidator(IEnumerable<(ContextType?, string)> contexts, IEnumerable invariants) + { + Contexts = contexts.ToList(); + Invariants = invariants.ToList(); + } + + internal List<(ContextType?, string)> Contexts { get; } + + internal List Invariants { get; } + + internal override ResultReport BasicValidate(IScopedNode input, ValidationSettings vc, ValidationState state) + { + if (Contexts.Any(c => c.Item1 == null)) + { + return new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INCORRECT, + "Extension context type was not set, but a context was defined. Skipping non-invariant context validation") + .AsResult(state); + } + + if (Contexts.TakeWhile(context => !validateContext(input, context, state)).Any()) + { + return new IssueAssertion(Issue.CONTENT_INCORRECT_OCCURRENCE, + $"Extension used outside of appropriate contexts. Expected context to be one of: {RenderExpectedContexts}") + .AsResult(state); + } + + var failedInvariantResults = Invariants + .Select(inv => runContextInvariant(input, inv, vc, state)) + .Where(res => !res.Success) + .ToList(); + + if (failedInvariantResults.Count != 0) + { + return ResultReport.Combine( + failedInvariantResults + .Where(fr => fr.Report is not null) + .Select(fr => fr.Report!) + .Concat( + failedInvariantResults + .Where(fr => fr.Report is null && fr.Success == false) + .Select(fr => + new IssueAssertion( + Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT, + $"Extension context failed invariant constraint {fr.Invariant}" + ).AsResult(state)) + ).ToList() + ); + } + + return ResultReport.SUCCESS; + } + + private bool validateContext(IScopedNode input, (ContextType?, string) context, ValidationState state) + { + var contextNode = input.ToScopedNode(); // TODO add parent once we update sdk to 5.10 + return context.Item1 switch + { + ContextType.RESOURCE => contextNode.Location.EndsWith(context.Item2), + ContextType.DATATYPE => contextNode.InstanceType == context.Item2, + ContextType.EXTENSION => contextNode.InstanceUri == context.Item2, // TODO this is wrong + ContextType.FHIRPATH => contextNode.IsTrue(context.Item2), + ContextType.ELEMENT => contextNode.Definition?.ElementName == context.Item2, + _ => throw new System.InvalidOperationException($"Unknown context type {context.Item1}") + }; + } + + private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) + { + var fhirPathValidator = new FhirPathValidator("ctx-inv", invariant); + return fhirPathValidator.RunInvariant(input, vc, state); + } + + private string RenderExpectedContexts => string.Join(", ", Contexts.Select(c => $"{{{c.Item1},{c.Item2}}}")); + + private string RenderExpectedInvariants => string.Join(" || ", Invariants); + + /// + protected override string Key => "context"; + + /// + protected override object Value => new JObject(); + + /// + public override JToken ToJson() => throw new System.NotImplementedException(); + + /// + /// The context in which the extension should be used. + /// + public enum ContextType + { + /// + /// The context is all elements matching a particular resource element path. + /// + RESOURCE, // STU3 + + /// + /// The context is all nodes matching a particular data type element path (root or repeating element) + /// or all elements referencing aparticular primitive data type (expressed as the datatype name). + /// + DATATYPE, // STU3 + + /// + /// The context is a particular extension from a particular profile, a uri that identifies the extension definition. + /// + EXTENSION, // STU3+ + + /// + /// The context is all elements that match the FHIRPath query found in the expression. + /// + FHIRPATH, // R4+ + + /// + /// The context is any element that has an ElementDefinition.id that matches that found in the expression. + /// This includes ElementDefinition Ids that have slicing identifiers. + /// The full path for the element is [url]#[elementid]. If there is no #, the Element id is one defined in the base specification. + /// + ELEMENT, // R4+ + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs index 6998e995..5766e721 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs @@ -39,19 +39,19 @@ public class FhirEle1Validator : InvariantValidator public override string? HumanDescription => "All FHIR elements must have a @value or children"; /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) { // Original R4B expression: "expression": "hasValue() or (children().count() > id.count()) or $this is Parameters", // Shortcut the evaluation if there is a value - if (input.Value is not null) return (true, null); + if (input.Value is not null) return new(true, null); // Shortcut the evaluation if this is a Parameters object - if (input.InstanceType == "Parameters") return (true, null); + if (input.InstanceType == "Parameters") return new(true, null); var hasOtherChildrenThanId = input.Children().SkipWhile(c => c.Name == "id").Any(); - return (hasOtherChildrenThanId, null); + return new(hasOtherChildrenThanId, null); } /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs index bb79bada..a76a95da 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs @@ -38,7 +38,7 @@ public class FhirExt1Validator : InvariantValidator public override string? HumanDescription => "Must have either extensions or value[x], not both"; /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) { // Original expression: "expression": "extension.exists() != value.exists()", @@ -53,7 +53,7 @@ internal override (bool, ResultReport?) RunInvariant(IScopedNode input, Validati if (hasExtension && hasValue) break; } - return (hasExtension != hasValue, null); + return new(hasExtension != hasValue, null); } /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 6a7352f9..115e6cbd 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -105,7 +105,7 @@ public override JToken ToJson() } /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) { try { @@ -114,13 +114,13 @@ internal override (bool, ResultReport?) RunInvariant(IScopedNode input, Validati { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) }; - return (predicate(node, context, vc), null); + return new InvariantResult(predicate(node, context, vc), null, Expression); } catch (Exception e) { - return (false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, + return new InvariantResult(false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") - .AsResult(s)); + .AsResult(s), Expression); } } diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index bd912b24..eb12c4c6 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -40,11 +40,11 @@ public class FhirTxt1Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, elements (either name or href), images and internally contained style attributes"; /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) { // Original expression: "expression": "htmlChecks()" - if (input.Value is null) return (false, null); + if (input.Value is null) return new(false, null); switch (input.Value) { @@ -54,16 +54,16 @@ internal override (bool, ResultReport?) RunInvariant(IScopedNode input, Validati if (result) { - return (true, null); + return new(true, null); } else { var issues = errors.Select(e => new IssueAssertion(Issue.XSD_VALIDATION_ERROR, e)); - return (false, new ResultReport(ValidationResult.Failure, issues)); + return new(false, new ResultReport(ValidationResult.Failure, issues)); } } default: - return (false, new ResultReport(ValidationResult.Failure, + return new(false, new ResultReport(ValidationResult.Failure, new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, $"Narrative should be of type string, but is of type ({input.Value.GetType()})"))); diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs index c176abe5..98def7c9 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -38,10 +38,10 @@ public class FhirTxt2Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL have some non-whitespace content"; /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState _) { //Check whether the narrative contains non-whitespace content. - return (!string.IsNullOrWhiteSpace(input.Value?.ToString()), null); + return new(!string.IsNullOrWhiteSpace(input.Value?.ToString()), null); } /// diff --git a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs index 96b710ce..547df660 100644 --- a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs @@ -56,20 +56,22 @@ public abstract class InvariantValidator : IValidatable /// public abstract JToken ToJson(); + + internal record InvariantResult(bool Success, ResultReport? Report, string? Invariant = null); /// /// Implements the logic for running the invariant. /// - internal abstract (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s); + internal abstract InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s); /// ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings vc, ValidationState s) { - var (success, directAssertion) = RunInvariant(input, vc, s); + var result = RunInvariant(input, vc, s); - if (directAssertion is not null) return directAssertion; + if (result.Report is not null) return result.Report; - if (!success) + if (!result.Success) { var sev = BestPractice ? vc.ConstraintBestPractices switch diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index c21348e5..5ed9f404 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -40,6 +40,11 @@ public class SliceValidator : IGroupValidatable [DataContract] public class SliceCase { + /// + /// Identifier for the slice. + /// + public string? Id { get; private set; } + /// /// Name of the slice. Used for diagnostic purposes. /// @@ -58,6 +63,21 @@ public class SliceCase [DataMember] public IAssertion Assertion { get; private set; } + /// + /// Construct a single in a . + /// + /// + /// + /// + /// + public SliceCase(string id, string name, IAssertion condition, IAssertion? assertion) + { + Id = id ?? throw new ArgumentNullException(nameof(id)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + Condition = condition ?? throw new ArgumentNullException(nameof(condition)); + Assertion = assertion ?? throw new ArgumentNullException(nameof(assertion)); + } + /// /// Construct a single in a . /// @@ -72,12 +92,17 @@ public SliceCase(string name, IAssertion condition, IAssertion? assertion) } /// - public JToken ToJson() => - new JObject( + public JToken ToJson() { + var token = new JObject( new JProperty("name", Name), new JProperty("condition", Condition.ToJson().MakeNestedProp()), new JProperty("assertion", Assertion.ToJson().MakeNestedProp()) - ); + ); + + if(Id is not null) token.AddFirst(new JProperty("id", Id)); + + return token; + } } /// diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index a6e21426..df16b39d 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -98,6 +98,14 @@ Firely.Fhir.Validation.DefinitionsAssertion.ToJson() -> Newtonsoft.Json.Linq.JTo Firely.Fhir.Validation.ElementSchema Firely.Fhir.Validation.ElementSchema.ElementSchema(Firely.Fhir.Validation.Canonical! id, params Firely.Fhir.Validation.IAssertion![]! members) -> void Firely.Fhir.Validation.ElementSchema.ElementSchema(Firely.Fhir.Validation.Canonical! id, System.Collections.Generic.IEnumerable! members) -> void +Firely.Fhir.Validation.ExtensionContextValidator +Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ContextType.DATATYPE = 1 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ContextType.ELEMENT = 4 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ContextType.EXTENSION = 2 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ContextType.FHIRPATH = 3 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ContextType.RESOURCE = 0 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType +Firely.Fhir.Validation.ExtensionContextValidator.ExtensionContextValidator(System.Collections.Generic.IEnumerable<(Firely.Fhir.Validation.ExtensionContextValidator.ContextType?, string!)>! contexts, System.Collections.Generic.IEnumerable! invariants) -> void Firely.Fhir.Validation.ExtensionSchema Firely.Fhir.Validation.ExtensionSchema.ExtensionSchema(Firely.Fhir.Validation.StructureDefinitionInformation! structureDefinition, params Firely.Fhir.Validation.IAssertion![]! members) -> void Firely.Fhir.Validation.ExtensionSchema.ExtensionSchema(Firely.Fhir.Validation.StructureDefinitionInformation! structureDefinition, System.Collections.Generic.IEnumerable! members) -> void @@ -240,7 +248,9 @@ Firely.Fhir.Validation.SliceValidator.Ordered.get -> bool Firely.Fhir.Validation.SliceValidator.SliceCase Firely.Fhir.Validation.SliceValidator.SliceCase.Assertion.get -> Firely.Fhir.Validation.IAssertion! Firely.Fhir.Validation.SliceValidator.SliceCase.Condition.get -> Firely.Fhir.Validation.IAssertion! +Firely.Fhir.Validation.SliceValidator.SliceCase.Id.get -> string? Firely.Fhir.Validation.SliceValidator.SliceCase.Name.get -> string! +Firely.Fhir.Validation.SliceValidator.SliceCase.SliceCase(string! id, string! name, Firely.Fhir.Validation.IAssertion! condition, Firely.Fhir.Validation.IAssertion? assertion) -> void Firely.Fhir.Validation.SliceValidator.SliceCase.SliceCase(string! name, Firely.Fhir.Validation.IAssertion! condition, Firely.Fhir.Validation.IAssertion? assertion) -> void Firely.Fhir.Validation.SliceValidator.SliceCase.ToJson() -> Newtonsoft.Json.Linq.JToken! Firely.Fhir.Validation.SliceValidator.Slices.get -> System.Collections.Generic.IReadOnlyList! @@ -301,6 +311,9 @@ Firely.Fhir.Validation.ValidationState.ValidationState(Firely.Fhir.Validation.Va override Firely.Fhir.Validation.Canonical.Equals(object? obj) -> bool override Firely.Fhir.Validation.Canonical.GetHashCode() -> int override Firely.Fhir.Validation.Canonical.ToString() -> string! +override Firely.Fhir.Validation.ExtensionContextValidator.Key.get -> string! +override Firely.Fhir.Validation.ExtensionContextValidator.ToJson() -> Newtonsoft.Json.Linq.JToken! +override Firely.Fhir.Validation.ExtensionContextValidator.Value.get -> object! override Firely.Fhir.Validation.FhirEle1Validator.BestPractice.get -> bool override Firely.Fhir.Validation.FhirEle1Validator.HumanDescription.get -> string? override Firely.Fhir.Validation.FhirEle1Validator.Key.get -> string! diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json index 5f2d7ba5..253e677f 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json @@ -110,6 +110,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -198,6 +199,7 @@ } }, { + "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json index 05d6eba4..d0dca4a8 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json @@ -719,6 +719,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -762,6 +763,7 @@ } }, { + "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -831,6 +833,7 @@ } }, { + "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json index bf682a57..7e6ec95a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json @@ -117,6 +117,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -205,6 +206,7 @@ } }, { + "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json index 3f3074f5..2950a9bc 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json @@ -726,6 +726,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -769,6 +770,7 @@ } }, { + "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -838,6 +840,7 @@ } }, { + "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json index 4df3d998..565ca8e1 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json @@ -110,6 +110,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -198,6 +199,7 @@ } }, { + "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json index 0ec983e9..dff2a4e8 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json @@ -700,6 +700,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Communication.payload:Attachment", "name": "Attachment", "condition": { "pathSelector": { @@ -743,6 +744,7 @@ } }, { + "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -812,6 +814,7 @@ } }, { + "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json index f42fb39e..ca2f20a1 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json @@ -94,6 +94,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Patient.identifier:fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -173,6 +174,7 @@ } }, { + "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json index 94fb09bd..819fb465 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json @@ -659,6 +659,7 @@ "defaultAtEnd": false, "case": [ { + "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -697,6 +698,7 @@ } }, { + "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -761,6 +763,7 @@ } }, { + "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index 31a06af2..45e21f01 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -35,8 +35,8 @@ public class BasicSchemaBuilderTests : IClassFixture public BasicSchemaBuilderTests(SchemaBuilderFixture fixture, ITestOutputHelper oh) => (_output, _fixture) = (oh, fixture); - [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] - // [Fact] + // [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] + [Fact] public void OverwriteSchemaSnaps() { compareToSchemaSnaps(true); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 64106cb6..0b996e83 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 64106cb6a31dbb3f9d81b1f7384b44b198409ca3 +Subproject commit 0b996e83cbeff56018e71e9c8fd2977356022cef diff --git a/test/Firely.Fhir.Validation.Tests/Impl/FhirUriValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/FhirUriValidatorTests.cs index 1fcab146..c8fce868 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/FhirUriValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/FhirUriValidatorTests.cs @@ -22,7 +22,6 @@ public class FhirUriValidatorTests : BasicValidatorTests public void TestFhirUriValidation() { var validator = new FhirUriValidator(); - const string PATTERN = "urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; base.BasicValidatorTestcases(validator, ElementNode.ForPrimitive("http://hl7.org/fhir"), true, null, "result must be true"); base.BasicValidatorTestcases(validator, ElementNode.ForPrimitive("http://hl7.org/fhir/StructureDefinition/regex"), true, null, "result must be true"); From e638a545d61f651869bf29aa2d51f6caf5f7c9fd Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 18 Sep 2024 10:10:33 +0200 Subject: [PATCH 02/25] Added tests and finalized implementation for extension context validation --- .../CommonExtensionContextComponent.cs | 20 +-- .../SchemaBuilders/ExtensionContextBuilder.cs | 2 +- .../Impl/ExtensionContextValidator.cs | 71 ++++++---- .../Impl/FhirPathValidator.cs | 47 +++++-- .../PublicAPI.Unshipped.txt | 14 +- .../Schema/DefinitionPath.cs | 4 + .../TestData/issue-165/fhirpkg.lock.json | 2 +- .../ExtensionContextComponentTests.cs | 24 ++++ ...idation.Compilation.Tests.Shared.projitems | 1 + .../TestProfileArtifactSource.cs | 29 ++++- .../Impl/ExtensionContextValidatorTests.cs | 121 ++++++++++++++++++ 11 files changed, 275 insertions(+), 60 deletions(-) create mode 100644 test/Firely.Fhir.Validation.Compilation.Tests.Shared/ExtensionContextComponentTests.cs create mode 100644 test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs b/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs index aa9bbc49..2e47aa9d 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/CommonExtensionContextComponent.cs @@ -6,20 +6,13 @@ using System.Linq; #pragma warning disable CS0618 // Type or member is obsolete +using TypedContext = Firely.Fhir.Validation.ExtensionContextValidator.TypedContext; + namespace Firely.Fhir.Validation.Compilation { - internal class CommonExtensionContextComponent + internal record CommonExtensionContextComponent(IEnumerable Contexts, + IEnumerable Invariants) { - internal CommonExtensionContextComponent(IEnumerable<(ExtensionContextValidator.ContextType?, string)> contexts, - IEnumerable invariants) // internal for testing purposes - { - Contexts = contexts; - Invariants = invariants; - } - - internal IEnumerable<(ExtensionContextValidator.ContextType?, string)> Contexts { get; private set; } - - internal IEnumerable Invariants { get; private set; } #if STU3 public static bool TryCreate(ElementDefinitionNavigator nav, [NotNullWhen(true)] out CommonExtensionContextComponent? result) @@ -40,7 +33,7 @@ public static bool TryCreate(ElementDefinitionNavigator nav, [NotNullWhen(true)] _ => throw new InvalidOperationException($"Unknown context type {strDef.ContextType.Value}") }; - var contexts = strDef.Context.Select(c => (contextType, c)); + var contexts = strDef.Context.Select(c => new TypedContext(contextType, c)); var invariants = strDef.ContextInvariant; @@ -57,7 +50,8 @@ public static bool TryCreate(ElementDefinitionNavigator def, [NotNullWhen(true)] return false; } - IEnumerable<(ExtensionContextValidator.ContextType?, string)> contexts = strDef.Context.Select(c => + var contexts = strDef.Context.Select(c => + new ( c.Type switch { diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs index 2b217bab..64380439 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs @@ -14,7 +14,7 @@ internal class ExtensionContextBuilder : ISchemaBuilder { public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode) { - if (nav is { Path: "StructureDefinition", StructureDefinition.Type: "Extension" } && CommonExtensionContextComponent.TryCreate(nav, out var trc)) + if (nav is { Path: "Extension", StructureDefinition.Type: "Extension" } && CommonExtensionContextComponent.TryCreate(nav, out var trc)) { yield return new ExtensionContextValidator(trc.Contexts, trc.Invariants); } diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index b22676cd..b5f96223 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -1,10 +1,9 @@ -using Hl7.Fhir.FhirPath; +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Support; using Hl7.FhirPath; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; @@ -21,26 +20,32 @@ namespace Firely.Fhir.Validation; #else [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] #endif -public class ExtensionContextValidator : BasicValidator +public class ExtensionContextValidator : IValidatable { /// /// Creates a new ExtensionContextValidator with the given allowed contexts and invariants. /// /// /// - public ExtensionContextValidator(IEnumerable<(ContextType?, string)> contexts, IEnumerable invariants) + public ExtensionContextValidator(IEnumerable contexts, IEnumerable invariants) { Contexts = contexts.ToList(); Invariants = invariants.ToList(); } - - internal List<(ContextType?, string)> Contexts { get; } + internal List Contexts { get; } internal List Invariants { get; } - internal override ResultReport BasicValidate(IScopedNode input, ValidationSettings vc, ValidationState state) + /// + /// Validate input against the expected context and invariants. + /// + /// + /// + /// + /// + public ResultReport Validate(IScopedNode input, ValidationSettings vc, ValidationState state) { - if (Contexts.Any(c => c.Item1 == null)) + if (Contexts.Any(c => c.Type == null)) { return new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INCORRECT, "Extension context type was not set, but a context was defined. Skipping non-invariant context validation") @@ -80,38 +85,50 @@ internal override ResultReport BasicValidate(IScopedNode input, ValidationSettin return ResultReport.SUCCESS; } - private bool validateContext(IScopedNode input, (ContextType?, string) context, ValidationState state) + private static bool validateContext(IScopedNode input, TypedContext context, ValidationState state) { - var contextNode = input.ToScopedNode(); // TODO add parent once we update sdk to 5.10 - return context.Item1 switch + var contextNode = input.ToScopedNode().Parent ?? throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); + return context.Type switch { - ContextType.RESOURCE => contextNode.Location.EndsWith(context.Item2), - ContextType.DATATYPE => contextNode.InstanceType == context.Item2, - ContextType.EXTENSION => contextNode.InstanceUri == context.Item2, // TODO this is wrong - ContextType.FHIRPATH => contextNode.IsTrue(context.Item2), - ContextType.ELEMENT => contextNode.Definition?.ElementName == context.Item2, - _ => throw new System.InvalidOperationException($"Unknown context type {context.Item1}") + ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression), + ContextType.DATATYPE => contextNode.InstanceType == context.Expression, + ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, + ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), + ContextType.ELEMENT => string.Join(".", state.Location.DefinitionPath.RenderAsElementId().Split('.')[..^1]) == context.Expression, // remove last evt, since we cannot render parent directly + _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") }; } private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) { - var fhirPathValidator = new FhirPathValidator("ctx-inv", invariant); - return fhirPathValidator.RunInvariant(input, vc, state); + var fhirPathValidator = new FhirPathValidator("ctx-inv", invariant.Replace("%extension", "%%extension")); + return fhirPathValidator.RunInvariant(input.ToScopedNode().Parent!, vc, state, ("extension", [input.ToScopedNode()])); } - private string RenderExpectedContexts => string.Join(", ", Contexts.Select(c => $"{{{c.Item1},{c.Item2}}}")); + private string RenderExpectedContexts => string.Join(", ", Contexts.Select(c => $"{{{c.Type},{c.Expression}}}")); - private string RenderExpectedInvariants => string.Join(" || ", Invariants); + private static string Key => "context"; - /// - protected override string Key => "context"; - - /// - protected override object Value => new JObject(); + private object Value => + new JObject( + new JProperty("context", new JArray(Contexts.Select(c => new JObject( + new JProperty("type", c.Expression), + new JProperty("expression", c.Expression) + )))), + new JProperty("invariants", new JArray(Invariants)) + ); /// - public override JToken ToJson() => throw new System.NotImplementedException(); + public JToken ToJson() => new JProperty(Key, Value); + + /// + /// + /// + /// + /// +#pragma warning disable RS0016 + public record TypedContext(ContextType? Type, string Expression); // PR TODO: ADD THESE TO PUBLIC API +#pragma warning restore RS0016 /// /// The context in which the extension should be used. diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 25fa3022..4cb7f821 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -16,7 +16,9 @@ using Hl7.FhirPath.Expressions; using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; using static Hl7.Fhir.Model.OperationOutcome; @@ -56,10 +58,7 @@ public class FhirPathValidator : InvariantValidator /// [DataMember] public override bool BestPractice => _bestPractice; - -#pragma warning disable IDE1006 // Naming Styles - private static readonly SymbolTable DefaultFpSymbolTable; -#pragma warning restore IDE1006 // Naming Styles + private readonly string _key; private readonly string? _humanDescription; private readonly IssueSeverity? _severity; @@ -105,22 +104,44 @@ public override JToken ToJson() } /// - internal override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) { try { ScopedNode node = input.ToScopedNode(); - var context = new FhirEvaluationContext() //%resource is the parent resource, but if we are in the root, it is the resource itself + + var context = new FhirEvaluationContext { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) }; var success = predicate(node, context, vc); - return (success, null); + return new(success, null); + } + catch (Exception e) + { + return new(false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, + $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") + .AsResult(s)); + } + } + + internal InvariantResult RunInvariant(ScopedNode input, ValidationSettings vc, ValidationState s, params (string key, IEnumerable value)[] env) + { + try + { + var context = new FhirEvaluationContext() + { + TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService), + Environment = new Dictionary>(env.Select(kvp => new KeyValuePair>(kvp.key, kvp.value))) + }; + + var success = predicate(input, context, vc); + return new(success, null); } catch (Exception e) { - return (false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, + return new(false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") .AsResult(s)); } @@ -134,17 +155,17 @@ internal override (bool, ResultReport?) RunInvariant(IScopedNode input, Validati static FhirPathValidator() { - DefaultFpSymbolTable = new SymbolTable(); - DefaultFpSymbolTable.AddStandardFP(); - DefaultFpSymbolTable.AddFhirExtensions(); + SymbolTable defaultFpSymbolTable = new(); + defaultFpSymbolTable.AddStandardFP(); + defaultFpSymbolTable.AddFhirExtensions(); // Until this method is included in a future release of the SDK // we need to add it ourselves. - DefaultFpSymbolTable.Add("conformsTo", (Func)conformsTo, doNullProp: false); + defaultFpSymbolTable.Add("conformsTo", (Func)conformsTo, doNullProp: false); static bool conformsTo(object focus, string valueset) => throw new NotImplementedException("The conformsTo() function is not supported in the .NET FhirPath engine."); - DefaultCompiler = new FhirPathCompiler(DefaultFpSymbolTable); + DefaultCompiler = new FhirPathCompiler(defaultFpSymbolTable); } private CompiledExpression getDefaultCompiledExpression(FhirPathCompiler compiler) diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index df16b39d..e344b4f1 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -105,7 +105,14 @@ Firely.Fhir.Validation.ExtensionContextValidator.ContextType.ELEMENT = 4 -> Fire Firely.Fhir.Validation.ExtensionContextValidator.ContextType.EXTENSION = 2 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType Firely.Fhir.Validation.ExtensionContextValidator.ContextType.FHIRPATH = 3 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType Firely.Fhir.Validation.ExtensionContextValidator.ContextType.RESOURCE = 0 -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType -Firely.Fhir.Validation.ExtensionContextValidator.ExtensionContextValidator(System.Collections.Generic.IEnumerable<(Firely.Fhir.Validation.ExtensionContextValidator.ContextType?, string!)>! contexts, System.Collections.Generic.IEnumerable! invariants) -> void +Firely.Fhir.Validation.ExtensionContextValidator.ExtensionContextValidator(System.Collections.Generic.IEnumerable! contexts, System.Collections.Generic.IEnumerable! invariants) -> void +Firely.Fhir.Validation.ExtensionContextValidator.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.ExtensionContextValidator.TypedContext +Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.Expression.get -> string! +Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.Expression.init -> void +Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.Type.get -> Firely.Fhir.Validation.ExtensionContextValidator.ContextType? +Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.Type.init -> void +Firely.Fhir.Validation.ExtensionContextValidator.Validate(Firely.Fhir.Validation.IScopedNode! input, Firely.Fhir.Validation.ValidationSettings! vc, Firely.Fhir.Validation.ValidationState! state) -> Firely.Fhir.Validation.ResultReport! Firely.Fhir.Validation.ExtensionSchema Firely.Fhir.Validation.ExtensionSchema.ExtensionSchema(Firely.Fhir.Validation.StructureDefinitionInformation! structureDefinition, params Firely.Fhir.Validation.IAssertion![]! members) -> void Firely.Fhir.Validation.ExtensionSchema.ExtensionSchema(Firely.Fhir.Validation.StructureDefinitionInformation! structureDefinition, System.Collections.Generic.IEnumerable! members) -> void @@ -311,9 +318,7 @@ Firely.Fhir.Validation.ValidationState.ValidationState(Firely.Fhir.Validation.Va override Firely.Fhir.Validation.Canonical.Equals(object? obj) -> bool override Firely.Fhir.Validation.Canonical.GetHashCode() -> int override Firely.Fhir.Validation.Canonical.ToString() -> string! -override Firely.Fhir.Validation.ExtensionContextValidator.Key.get -> string! -override Firely.Fhir.Validation.ExtensionContextValidator.ToJson() -> Newtonsoft.Json.Linq.JToken! -override Firely.Fhir.Validation.ExtensionContextValidator.Value.get -> object! +override Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.ToString() -> string! override Firely.Fhir.Validation.FhirEle1Validator.BestPractice.get -> bool override Firely.Fhir.Validation.FhirEle1Validator.HumanDescription.get -> string? override Firely.Fhir.Validation.FhirEle1Validator.Key.get -> string! @@ -393,6 +398,7 @@ virtual Firely.Fhir.Validation.Canonical.EqualityContract.get -> System.Type! virtual Firely.Fhir.Validation.Canonical.Equals(Firely.Fhir.Validation.Canonical? other) -> bool virtual Firely.Fhir.Validation.Canonical.PrintMembers(System.Text.StringBuilder! builder) -> bool virtual Firely.Fhir.Validation.ElementSchema.ToJson() -> Newtonsoft.Json.Linq.JToken! +virtual Firely.Fhir.Validation.ExtensionContextValidator.TypedContext.EqualityContract.get -> System.Type! virtual Firely.Fhir.Validation.ExtensionUrlFollower.Invoke(string! location, Firely.Fhir.Validation.Canonical? url) -> Firely.Fhir.Validation.ExtensionUrlHandling virtual Firely.Fhir.Validation.MetaProfileSelector.Invoke(string! location, Firely.Fhir.Validation.Canonical![]! originalProfiles) -> Firely.Fhir.Validation.Canonical![]! virtual Firely.Fhir.Validation.StructureDefinitionInformation.$() -> Firely.Fhir.Validation.StructureDefinitionInformation! diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index a344a62a..37785b48 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -6,6 +6,8 @@ * available at https://github.com/FirelyTeam/firely-validator-api/blob/main/LICENSE */ +using System.Text.RegularExpressions; + namespace Firely.Fhir.Validation { /// @@ -39,6 +41,8 @@ public bool HasDefinitionChoiceInformation return false; } } + + internal string RenderAsElementId() => Regex.Replace(ToString().Replace("->", ""), "\\(.*\\)", ""); // remove profiles /// public override string ToString() diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json index fb1c6be6..ad3b2901 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json @@ -1,5 +1,5 @@ { - "updated": "2024-08-22T14:30:50.773281+02:00", + "updated": "2024-09-17T11:08:44.818526+02:00", "dependencies": { "kbv.ita.erp": "1.0.2", "hl7.fhir.r4.core": "4.0.1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ExtensionContextComponentTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ExtensionContextComponentTests.cs new file mode 100644 index 00000000..cf155164 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ExtensionContextComponentTests.cs @@ -0,0 +1,24 @@ +using FluentAssertions; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using Xunit; + +namespace Firely.Fhir.Validation.Compilation.Tests; + +public class ExtensionContextComponentTests : IClassFixture +{ + internal SchemaBuilderFixture _fixture; + + public ExtensionContextComponentTests(SchemaBuilderFixture fixture) => _fixture = fixture; + + [Fact] + public void CreatesExtensionContextSchema() + { + var schema = _fixture.SchemaResolver.GetSchema(TestProfileArtifactSource.CONTEXTCONSTRAINEDEXTENSION); + schema.Should().NotBeNull(); + var json = schema.ToJson(); + + json["context"].Should().NotBeNull(); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems index da1d7552..634368ff 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems @@ -15,6 +15,7 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs index 638f1a7f..9ce81049 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs @@ -43,6 +43,8 @@ internal class TestProfileArtifactSource : IResourceResolver public const string PROFILEDSTRING = "http://validationtest.org/fhir/StructureDefinition/stringProfile"; public const string PATIENTWITHPROFILEDREFS = "http://validationtest.org/fhir/StructureDefinition/PatientWithReferences"; public const string BUNDLEWITHCONSTRAINEDCONTAINED = "http://validationtest.org/fhir/StructureDefinition/BundleWithConstrainedContained"; + + public const string CONTEXTCONSTRAINEDEXTENSION = "http://validationtest.org/fhir/StructureDefinition/ConstrainedExtensionContext"; public List TestProfiles = @@ -72,7 +74,8 @@ internal class TestProfileArtifactSource : IResourceResolver createTestSD(PROFILEDBOOL, "NoopBoolProfile", "A noop profile for a bool", FHIRAllTypes.Boolean), buildPatientWithProfiledReferences(), bundleWithConstrainedContained(), - buildProfiledEncounter() + buildProfiledEncounter(), + buildContextConstrainedExtension() ]; private static StructureDefinition buildProfiledEncounter() @@ -560,6 +563,30 @@ private static StructureDefinition bundleWithConstrainedContained() return result; } + + private static StructureDefinition buildContextConstrainedExtension() + { + var result = createTestSD( + CONTEXTCONSTRAINEDEXTENSION, + "Extension with constraints on context", + "Extension with constraints on context", + FHIRAllTypes.Extension + ); + + var cons = result.Differential.Element; + +#if STU3 + result.ContextType = StructureDefinition.ExtensionContext.Datatype; + result.Context = ["http://example.org/MyExtension", "DateTime"]; +#else + result.Context = [ + new StructureDefinition.ContextComponent { Type = StructureDefinition.ExtensionContextType.Extension, Expression = "http://example.org/MyExtension" }, + new StructureDefinition.ContextComponent { Type = StructureDefinition.ExtensionContextType.Element, Expression = "Observation.valueDateTime" } + ]; +#endif + result.ContextInvariant = ["true", "false"]; + return result; + } private static StructureDefinition createTestSD(string url, string name, string description, FHIRAllTypes constrainedType, string? baseUri = null) { diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs new file mode 100644 index 00000000..a8e49b0f --- /dev/null +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -0,0 +1,121 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation.Tests; + +[TestClass] +public class ExtensionContextValidatorTests +{ + [DataTestMethod] + [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "boolean", true)] + [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "string", false)] + [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "active[0]", true)] + [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "OperationOutcome", false)] + [DataRow(ExtensionContextValidator.ContextType.EXTENSION, "http://example.org/extensions#test", false)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Patient.active", true)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Practitioner.active", false)] + [DataRow(ExtensionContextValidator.ContextType.FHIRPATH, "active.exists()", true)] + [DataRow(ExtensionContextValidator.ContextType.FHIRPATH, "name.exists()", false)] + public void Extension_UsedInContext_ValidatesCorrectly(ExtensionContextValidator.ContextType cType, string expr, bool expected) + { + var ctxValidator = new ExtensionContextValidator( + [new(cType, expr)], + ["true"] // only look at contexts + ); + + AssertAgainstContextValidator(ctxValidator, expected); + } + + [DataTestMethod] + [DataRow(false, "true", "true", "false")] + [DataRow(true, "extension.exists()")] + [DataRow(true, "extension = %extension")] + [DataRow(false, "active.exists()")] + public void Extension_WithContextInvariants_ValidatesCorrectly(bool expected, params string[] invariants) + { + var validator = new ExtensionContextValidator( + [], + invariants + ); + + AssertAgainstContextValidator(validator, expected); + } + + [DataTestMethod] + [DataRow(true, "http://example.org/extensions#test")] + [DataRow(false, "http://example.org/extensions#testnested")] + public void Extension_WithExtensionContext_ValidatesCorrectly(bool expected, string url) + { + var schema = new ResourceSchema( + new StructureDefinitionInformation( + "http://test.org/testpat", + null, + "Patient", + StructureDefinitionInformation.TypeDerivationRule.Constraint, + false + )); + + var ctxValidator = new ExtensionContextValidator( + [new(ExtensionContextValidator.ContextType.EXTENSION, url)], + ["true"] + ); + + var validator = new ChildrenValidator([ + ("active", + new ChildrenValidator([ + ("extension", + new ChildrenValidator([ + ("value", + new ChildrenValidator([("extension", ctxValidator)]) + ) + ]){AllowAdditionalChildren = true} + ) + ]){AllowAdditionalChildren = true} + ) + ]); + + var pat = new Patient { ActiveElement = new FhirBoolean() }; + var uriWithExt = new FhirBoolean(false); + uriWithExt.AddExtension("http://example.org/extensions#testnested", new FhirString("unknown")); + pat.ActiveElement.AddExtension("http://example.org/extensions#test", uriWithExt); + + var result = validator.Validate( + pat + .ToTypedElement(), + new ValidationSettings(), + new ValidationState { Location = { DefinitionPath = DefinitionPath.Start().InvokeSchema(schema) } } + ); + + Assert.AreEqual(result.IsSuccessful, expected); + } + + private void AssertAgainstContextValidator(ExtensionContextValidator ctxValidator, bool expectedResult) + { + var schema = new ResourceSchema( + new StructureDefinitionInformation( + "http://test.org/testpat", + null, + "Patient", + StructureDefinitionInformation.TypeDerivationRule.Constraint, + false + )); + var validator = new ChildrenValidator([("active", new ChildrenValidator([("extension", ctxValidator)]))]); + + var pat = new Patient { ActiveElement = new FhirBoolean() }; + + pat.ActiveElement.AddExtension("http://example.org/extensions#test", new FhirString("unknown")); + + var result = validator.Validate( + pat + .ToTypedElement(), + new ValidationSettings(), + new ValidationState { Location = { DefinitionPath = DefinitionPath.Start().InvokeSchema(schema) } } + ); + + Assert.AreEqual(result.IsSuccessful, expectedResult); + } +} \ No newline at end of file From fbaeefaaafed78a78ef5b1de1b1907a671ea333f Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 18 Sep 2024 16:45:15 +0200 Subject: [PATCH 03/25] wip --- .../Impl/ExtensionContextValidator.cs | 52 ++++++++++++++----- .../Schema/DefinitionPath.cs | 2 +- .../FhirTestCases | 2 +- .../FhirTests/ValidationManifestTest.cs | 2 +- .../Impl/ExtensionContextValidatorTests.cs | 23 +++++--- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index b5f96223..d92f3dc8 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Hl7.FhirPath; using Newtonsoft.Json.Linq; @@ -32,6 +33,7 @@ public ExtensionContextValidator(IEnumerable contexts, IEnumerable Contexts = contexts.ToList(); Invariants = invariants.ToList(); } + internal List Contexts { get; } internal List Invariants { get; } @@ -52,13 +54,13 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .AsResult(state); } - if (Contexts.TakeWhile(context => !validateContext(input, context, state)).Any()) + if (Contexts.TakeWhile(context => !validateContext(input, context, vc, state).IsSuccessful).Any()) { return new IssueAssertion(Issue.CONTENT_INCORRECT_OCCURRENCE, $"Extension used outside of appropriate contexts. Expected context to be one of: {RenderExpectedContexts}") .AsResult(state); } - + var failedInvariantResults = Invariants .Select(inv => runContextInvariant(input, inv, vc, state)) .Where(res => !res.Success) @@ -75,7 +77,7 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .Where(fr => fr.Report is null && fr.Success == false) .Select(fr => new IssueAssertion( - Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT, + Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT, $"Extension context failed invariant constraint {fr.Invariant}" ).AsResult(state)) ).ToList() @@ -85,20 +87,46 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio return ResultReport.SUCCESS; } - private static bool validateContext(IScopedNode input, TypedContext context, ValidationState state) + private static ResultReport validateContext(IScopedNode input, TypedContext context, ValidationSettings settings, ValidationState state) { - var contextNode = input.ToScopedNode().Parent ?? throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); + var contextNode = input.ToScopedNode().Parent ?? + throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); return context.Type switch { - ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression), - ContextType.DATATYPE => contextNode.InstanceType == context.Expression, - ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, - ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), - ContextType.ELEMENT => string.Join(".", state.Location.DefinitionPath.RenderAsElementId().Split('.')[..^1]) == context.Expression, // remove last evt, since we cannot render parent directly + ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression) ? ResultReport.SUCCESS : ResultReport.FAILURE, + ContextType.DATATYPE => contextNode.InstanceType == context.Expression ? ResultReport.SUCCESS : ResultReport.FAILURE, + ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression + ? ResultReport.SUCCESS + : ResultReport.FAILURE, + ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression) ? ResultReport.SUCCESS : ResultReport.FAILURE, + ContextType.ELEMENT => validateElementContext(contextNode, context.Expression, settings, state), _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") }; } + private static ResultReport validateElementContext(ScopedNode contextNode, string contextExpression, ValidationSettings settings, ValidationState state) + { + var ctxSchema = settings.ElementSchemaResolver.GetSchema(Canonical.ForCoreType(contextNode.InstanceType!)); + + if (ctxSchema is null) + { + return string.Join('.', getContextPathElementsFromState(state)[..^1]) == contextExpression + ? ResultReport.SUCCESS + : new IssueAssertion(Issue.UNAVAILABLE_REFERENCED_PROFILE, $"Could not find schema for type {contextNode.InstanceType}").AsResult(state); + } + + var baseTypes = getBaseTypesFromSchema((FhirSchema)ctxSchema); + var contextPathWithoutType = getContextPathElementsFromState(state).Length < 2 ? "" : '.' + string.Join('.', getContextPathElementsFromState(state)[1..^1]); + return baseTypes.Any(type => contextExpression == $"{type}{contextPathWithoutType}" || type == contextExpression) ? ResultReport.SUCCESS : ResultReport.FAILURE; + + static IEnumerable getBaseTypesFromSchema(FhirSchema schema) => + schema.StructureDefinition.BaseCanonicals! + .Append(schema.StructureDefinition.Canonical) + .Select(canonical => canonical.Uri!.Split('/').Last()); + + static string[] getContextPathElementsFromState(ValidationState state) => state.Location.DefinitionPath.RenderAsElementId().Split('.'); + } + private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) { var fhirPathValidator = new FhirPathValidator("ctx-inv", invariant.Replace("%extension", "%%extension")); @@ -109,7 +137,7 @@ private static InvariantValidator.InvariantResult runContextInvariant(IScopedNod private static string Key => "context"; - private object Value => + private object Value => new JObject( new JProperty("context", new JArray(Contexts.Select(c => new JObject( new JProperty("type", c.Expression), @@ -120,7 +148,7 @@ private static InvariantValidator.InvariantResult runContextInvariant(IScopedNod /// public JToken ToJson() => new JProperty(Key, Value); - + /// /// /// diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index 37785b48..db8e8b1f 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -42,7 +42,7 @@ public bool HasDefinitionChoiceInformation } } - internal string RenderAsElementId() => Regex.Replace(ToString().Replace("->", ""), "\\(.*\\)", ""); // remove profiles + internal string RenderAsElementId() => Regex.Replace(ToString().Replace("->", "."), "\\(.*\\)", ""); // remove profiles /// public override string ToString() diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 0b996e83..58fb39c0 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 0b996e83cbeff56018e71e9c8fd2977356022cef +Subproject commit 58fb39c0127c38242b5c38cdc6a62dcef8b40638 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index ec4540cc..94eb56c6 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -59,7 +59,7 @@ public void RunFirelySdkTests(TestCase testCase, string baseDirectory) /// - The method `ClassCleanup` will gather all the testcases and serialize those to disk. The filename can be altered in /// that method /// - [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] + // [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] [TestMethod] public void AddFirelySdkValidatorResults() => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { DotNetValidator.Create() }); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs index a8e49b0f..c6670518 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -1,23 +1,30 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; +using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; namespace Firely.Fhir.Validation.Tests; [TestClass] public class ExtensionContextValidatorTests { + private IElementSchemaResolver _schemaResolver = new TestResolver([new DatatypeSchema(new StructureDefinitionInformation( + "http://hl7.org/fhir/StructureDefinition/boolean", + ["http://hl7.org/fhir/StructureDefintion/DataType", "http://hl7.org/fhir/StructureDefinition/Element", "http://hl7.org/fhir/StructureDefinition/Base"], + "boolean", + StructureDefinitionInformation.TypeDerivationRule.Constraint, + false))]); + [DataTestMethod] [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "boolean", true)] [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "string", false)] [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "active[0]", true)] [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "OperationOutcome", false)] [DataRow(ExtensionContextValidator.ContextType.EXTENSION, "http://example.org/extensions#test", false)] - [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Patient.active", true)] - [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Practitioner.active", false)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "boolean", true)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Element", true)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Resource", false)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "string", false)] [DataRow(ExtensionContextValidator.ContextType.FHIRPATH, "active.exists()", true)] [DataRow(ExtensionContextValidator.ContextType.FHIRPATH, "name.exists()", false)] public void Extension_UsedInContext_ValidatesCorrectly(ExtensionContextValidator.ContextType cType, string expr, bool expected) @@ -86,7 +93,7 @@ public void Extension_WithExtensionContext_ValidatesCorrectly(bool expected, str var result = validator.Validate( pat .ToTypedElement(), - new ValidationSettings(), + new ValidationSettings{}, new ValidationState { Location = { DefinitionPath = DefinitionPath.Start().InvokeSchema(schema) } } ); @@ -112,10 +119,10 @@ private void AssertAgainstContextValidator(ExtensionContextValidator ctxValidato var result = validator.Validate( pat .ToTypedElement(), - new ValidationSettings(), + new ValidationSettings() {ElementSchemaResolver = _schemaResolver}, new ValidationState { Location = { DefinitionPath = DefinitionPath.Start().InvokeSchema(schema) } } ); - Assert.AreEqual(result.IsSuccessful, expectedResult); + Assert.AreEqual(expectedResult, result.IsSuccessful); } } \ No newline at end of file From 2b56df49618fd170c6820f838b292ff19bd693f3 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 18 Sep 2024 17:31:23 +0200 Subject: [PATCH 04/25] a bit better --- src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs | 2 +- src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs | 2 +- .../FhirTests/ValidationManifestTest.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index d92f3dc8..f2f168dc 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -54,7 +54,7 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .AsResult(state); } - if (Contexts.TakeWhile(context => !validateContext(input, context, vc, state).IsSuccessful).Any()) + if (Contexts.Count > 0 && !Contexts.Any(context => validateContext(input, context, vc, state).IsSuccessful)) { return new IssueAssertion(Issue.CONTENT_INCORRECT_OCCURRENCE, $"Extension used outside of appropriate contexts. Expected context to be one of: {RenderExpectedContexts}") diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 4cb7f821..aeded1d7 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -116,7 +116,7 @@ internal override InvariantResult RunInvariant(IScopedNode input, ValidationSett }; var success = predicate(node, context, vc); - return new(success, null); + return new(success, null, Expression); } catch (Exception e) { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index 94eb56c6..ff741bfe 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -32,7 +32,7 @@ public class ValidationManifestTest /// //[Ignore] [DataTestMethod] - [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "mr-m-simple-nossystem")] + [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "ext-ctxt-good-name")] public void RunSingleTest(TestCase testCase, string baseDirectory) => _runner.RunTestCase(testCase, DotNetValidator.Create(), baseDirectory); From d6b64c852f915e2389ed54ae59d868224005695a Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Thu, 19 Sep 2024 12:43:46 +0200 Subject: [PATCH 05/25] more progress, only the slices remain --- .../Impl/ExtensionContextValidator.cs | 41 +++++--------- .../Schema/DefinitionPath.cs | 54 ++++++++++++++++--- .../FhirTests/ValidationManifestTest.cs | 2 +- .../Impl/ExtensionContextValidatorTests.cs | 7 +-- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index f2f168dc..c1e82477 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -54,7 +54,7 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .AsResult(state); } - if (Contexts.Count > 0 && !Contexts.Any(context => validateContext(input, context, vc, state).IsSuccessful)) + if (Contexts.Count > 0 && !Contexts.Any(context => validateContext(input, context, vc, state))) { return new IssueAssertion(Issue.CONTENT_INCORRECT_OCCURRENCE, $"Extension used outside of appropriate contexts. Expected context to be one of: {RenderExpectedContexts}") @@ -87,44 +87,29 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio return ResultReport.SUCCESS; } - private static ResultReport validateContext(IScopedNode input, TypedContext context, ValidationSettings settings, ValidationState state) + private static bool validateContext(IScopedNode input, TypedContext context, ValidationSettings settings, ValidationState state) { var contextNode = input.ToScopedNode().Parent ?? throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); return context.Type switch { - ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression) ? ResultReport.SUCCESS : ResultReport.FAILURE, - ContextType.DATATYPE => contextNode.InstanceType == context.Expression ? ResultReport.SUCCESS : ResultReport.FAILURE, - ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression - ? ResultReport.SUCCESS - : ResultReport.FAILURE, - ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression) ? ResultReport.SUCCESS : ResultReport.FAILURE, - ContextType.ELEMENT => validateElementContext(contextNode, context.Expression, settings, state), + ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression), + ContextType.DATATYPE => contextNode.InstanceType == context.Expression, + ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, + ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), + ContextType.ELEMENT => validateElementContext(context.Expression, state), _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") }; } - private static ResultReport validateElementContext(ScopedNode contextNode, string contextExpression, ValidationSettings settings, ValidationState state) + private static bool validateElementContext(string contextExpression, ValidationState state) { - var ctxSchema = settings.ElementSchemaResolver.GetSchema(Canonical.ForCoreType(contextNode.InstanceType!)); + var defPath = state.Location.DefinitionPath; + var needsElementAdded = defPath.Current?.Previous is not InvokeProfileEvent; - if (ctxSchema is null) - { - return string.Join('.', getContextPathElementsFromState(state)[..^1]) == contextExpression - ? ResultReport.SUCCESS - : new IssueAssertion(Issue.UNAVAILABLE_REFERENCED_PROFILE, $"Could not find schema for type {contextNode.InstanceType}").AsResult(state); - } - - var baseTypes = getBaseTypesFromSchema((FhirSchema)ctxSchema); - var contextPathWithoutType = getContextPathElementsFromState(state).Length < 2 ? "" : '.' + string.Join('.', getContextPathElementsFromState(state)[1..^1]); - return baseTypes.Any(type => contextExpression == $"{type}{contextPathWithoutType}" || type == contextExpression) ? ResultReport.SUCCESS : ResultReport.FAILURE; - - static IEnumerable getBaseTypesFromSchema(FhirSchema schema) => - schema.StructureDefinition.BaseCanonicals! - .Append(schema.StructureDefinition.Canonical) - .Select(canonical => canonical.Uri!.Split('/').Last()); - - static string[] getContextPathElementsFromState(ValidationState state) => state.Location.DefinitionPath.RenderAsElementId().Split('.'); + return + defPath.GetAllPossibleElementIds().Any(eid => eid == contextExpression + ".extension") || + needsElementAdded && contextExpression is "Element" or "Base"; } private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index db8e8b1f..ff579516 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -1,11 +1,14 @@ -/* +/* * Copyright (c) 2024, Firely (info@fire.ly) and contributors * See the file CONTRIBUTORS for details. - * + * * This file is licensed under the BSD 3-Clause license * available at https://github.com/FirelyTeam/firely-validator-api/blob/main/LICENSE */ +using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace Firely.Fhir.Validation @@ -41,8 +44,43 @@ public bool HasDefinitionChoiceInformation return false; } } - - internal string RenderAsElementId() => Regex.Replace(ToString().Replace("->", "."), "\\(.*\\)", ""); // remove profiles + + internal IEnumerable GetAllPossibleElementIds() + { + // if the previous event was not an invoke profile event, we need to explicitly include base and element (it cannot be a resource) or a complex element + var needsElementAdded = Current?.Previous is not InvokeProfileEvent; + return getPossibleElementIds(Current).Concat(needsElementAdded ? ["Base.extension", "Element.extension"] : []); + + IEnumerable getPossibleElementIds(PathStackEvent? e, string current = "") + { + return e switch + { + null => [current], + ChildNavEvent cne => getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}"), + CheckSliceEvent cse => getPossibleElementIds(cse.Previous, $":{cse.SliceName}{current}"), + InvokeProfileEvent ipe => + (ipe.Previous is not null + ? getPossibleElementIds(ipe.Previous, current) + : Enumerable.Empty()) + .Concat + ( +#pragma warning disable CS0618 // Type or member is obsolete + getOwnAndBaseTypesFromSchema((FhirSchema)ipe.Schema).Select(type => $"{type}{current}") +#pragma warning restore CS0618 // Type or member is obsolete + ), + _ => throw new InvalidOperationException("Unexpected event type") + }; + } + +#pragma warning disable CS0618 // Type or member is obsolete + IEnumerable getOwnAndBaseTypesFromSchema(FhirSchema schema) + { +#pragma warning restore CS0618 // Type or member is obsolete + return (schema.StructureDefinition.BaseCanonicals ?? Enumerable.Empty()) + .Append(schema.StructureDefinition.Canonical) + .Select(canonical => canonical.Uri!.Split('/').Last()); + } + } /// public override string ToString() @@ -50,9 +88,9 @@ public override string ToString() return Current is not null ? render(Current) : string.Empty; static string render(PathStackEvent e) => - e.Previous is not null ? - combine(render(e.Previous), e) - : e.Render(); + e.Previous is not null + ? combine(render(e.Previous), e) + : e.Render(); static string combine(string left, PathStackEvent right) => right is InvokeProfileEvent ipe ? left + "->" + ipe.Render() : left + right.Render(); @@ -106,4 +144,4 @@ public bool TryGetSliceInfo(out string? sliceInfo) /// public DefinitionPath CheckSlice(string sliceName) => new(new CheckSliceEvent(Current, sliceName)); } -} +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index ff741bfe..a1fa255c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -32,7 +32,7 @@ public class ValidationManifestTest /// //[Ignore] [DataTestMethod] - [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "ext-ctxt-good-name")] + [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "no/Person-test-bad")] public void RunSingleTest(TestCase testCase, string baseDirectory) => _runner.RunTestCase(testCase, DotNetValidator.Create(), baseDirectory); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs index c6670518..0cda3218 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -21,7 +21,8 @@ public class ExtensionContextValidatorTests [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "active[0]", true)] [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "OperationOutcome", false)] [DataRow(ExtensionContextValidator.ContextType.EXTENSION, "http://example.org/extensions#test", false)] - [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "boolean", true)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "boolean", false)] + [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Resource.active", true)] [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Element", true)] [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "Resource", false)] [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "string", false)] @@ -104,8 +105,8 @@ private void AssertAgainstContextValidator(ExtensionContextValidator ctxValidato { var schema = new ResourceSchema( new StructureDefinitionInformation( - "http://test.org/testpat", - null, + "http://test.org/Patient", + ["http://hl7.org/fhir/StructureDefinition/DomainResource", "http://hl7.org/fhir/StructureDefinition/Resource", "http://hl7.org/fhir/StructureDefinition/Base"], "Patient", StructureDefinitionInformation.TypeDerivationRule.Constraint, false From fe8f3e422f8299cf0b0cae58a643a5ed038841fd Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Thu, 19 Sep 2024 14:08:05 +0200 Subject: [PATCH 06/25] correct link --- .../FhirTestCases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 58fb39c0..2917dbc9 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 58fb39c0127c38242b5c38cdc6a62dcef8b40638 +Subproject commit 2917dbc92d215d6f4fe03b5177e8bfb1cfef2693 From bdc85ba54e0662db819bcfb3a15a4612e6a9ccb1 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 24 Sep 2024 10:41:04 +0200 Subject: [PATCH 07/25] hopefully fixed slicing --- .../Impl/ExtensionContextValidator.cs | 7 ++++++- src/Firely.Fhir.Validation/Schema/DefinitionPath.cs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index c1e82477..b49a40ff 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -106,9 +106,14 @@ private static bool validateElementContext(string contextExpression, ValidationS { var defPath = state.Location.DefinitionPath; var needsElementAdded = defPath.Current?.Previous is not InvokeProfileEvent; + + // if we have a slicing identifier, this is a bit tougher, since the slicing identifier actually defines the element itself, not its context. consider: + // context without slicing: Address + // context with slicing: Address.extension:simpleExtension + var exprHasSlicingIdentifier = contextExpression.Contains(':'); return - defPath.GetAllPossibleElementIds().Any(eid => eid == contextExpression + ".extension") || + defPath.GetAllPossibleElementIds().Any(eid => eid == (exprHasSlicingIdentifier ? contextExpression : contextExpression + ".extension")) || needsElementAdded && contextExpression is "Element" or "Base"; } diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index ff579516..a4e55a08 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -57,11 +57,11 @@ IEnumerable getPossibleElementIds(PathStackEvent? e, string current = "" { null => [current], ChildNavEvent cne => getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}"), - CheckSliceEvent cse => getPossibleElementIds(cse.Previous, $":{cse.SliceName}{current}"), + CheckSliceEvent cse => getPossibleElementIds(cse.Previous, $":{cse.SliceName}{current}").Concat(getPossibleElementIds(cse.Previous, current)), InvokeProfileEvent ipe => (ipe.Previous is not null ? getPossibleElementIds(ipe.Previous, current) - : Enumerable.Empty()) + : []) .Concat ( #pragma warning disable CS0618 // Type or member is obsolete From 93e3f521aed798e7abe2744f88398a2f755ab383 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 24 Sep 2024 10:41:40 +0200 Subject: [PATCH 08/25] forgot to commit --- .../FhirTestCases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 2917dbc9..99bf0f18 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 2917dbc92d215d6f4fe03b5177e8bfb1cfef2693 +Subproject commit 99bf0f180abfdc87461216f94214c4387179341f From 2f9254c66d18c44f45eddd0b25ce7ba02812cd71 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 1 Oct 2024 14:59:55 +0200 Subject: [PATCH 09/25] added backboneElement refs to schema snaps --- .../SchemaBuilders/TypeReferenceBuilder.cs | 3 +++ .../SchemaSnaps/Bundle.json | 5 +++++ .../SchemaSnaps/Composition.json | 4 ++++ .../SchemaSnaps/Patient.json | 3 +++ .../SchemaSnaps/PatternSlice.json | 3 +++ .../SchemaSnaps/ProfiledObservation.json | 2 ++ .../SchemaSnaps/ProfiledQuestionnaire.json | 4 ++++ .../SchemaSnaps/Questionnaire.json | 4 ++++ .../SchemaSnaps/SecondaryTypeRefSlice.json | 4 ++++ .../FhirTests/ValidationManifestTest.cs | 2 +- 10 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 61cef159..2f29fed7 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -85,6 +85,9 @@ private static bool shouldValidateTypeReference(ElementDefinitionNavigator nav) //if there are no children, we should validate against the type reference, because that's where we find the child constraints. if (!nav.HasChildren) return true; + + //if this is a backbone element, we should validate against the base backbone element, since this validation would be skipped if we only validate the children it is extended with. + if (def.IsBackboneElement()) return true; //if there are children, we should only validate against the type reference if there are profile details. #if STU3 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json index cef5c85d..d4c1682b 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json @@ -191,6 +191,7 @@ "humanDescription": "must be a resource unless there's a request or response" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Bundle.entry.id", @@ -231,6 +232,7 @@ "id": "#Bundle.entry.search", "FastInvariant-ele1": {}, "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Bundle.entry.search.id", @@ -274,6 +276,7 @@ "id": "#Bundle.entry.request", "FastInvariant-ele1": {}, "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Bundle.entry.request.id", @@ -341,6 +344,7 @@ "id": "#Bundle.entry.response", "FastInvariant-ele1": {}, "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Bundle.entry.response.id", @@ -404,6 +408,7 @@ "definitions": [ { "id": "#Bundle.link", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Bundle.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json index f75e7770..d198b1fb 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json @@ -305,6 +305,7 @@ "id": "#Composition.attester", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Composition.attester.id", @@ -454,6 +455,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Composition.relatesTo.id", @@ -539,6 +541,7 @@ "id": "#Composition.event", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Composition.event.id", @@ -633,6 +636,7 @@ "definitions": [ { "id": "#Composition.section", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Composition.section.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json index d5277427..43a4276b 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json @@ -263,6 +263,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -373,6 +374,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -504,6 +506,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json index 253e677f..b373c700 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json @@ -460,6 +460,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -549,6 +550,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -666,6 +668,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json index d6704114..d98f0b0c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json @@ -820,6 +820,7 @@ "humanDescription": "Must have at least a low or a high or text" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Observation.referenceRange.id", @@ -1033,6 +1034,7 @@ "id": "#Observation.component", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Observation.component.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json index 3657e764..fde3296d 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json @@ -341,6 +341,7 @@ "humanDescription": "Can only have multiple initial values for repeating items" }, "cardinality": "1..100", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -418,6 +419,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -627,6 +629,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -748,6 +751,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json index a6d8c583..73356efc 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json @@ -354,6 +354,7 @@ "definitions": [ { "id": "#Questionnaire.item", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -431,6 +432,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -640,6 +642,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -761,6 +764,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json index d0dca4a8..751819a7 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json @@ -614,6 +614,7 @@ "id": "#Communication.payload", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -733,6 +734,7 @@ "id": "#Communication.payload:String", "FastInvariant-ele1": {}, "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -792,6 +794,7 @@ "id": "#Communication.payload:DocumentReference", "FastInvariant-ele1": {}, "cardinality": "1..20", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -862,6 +865,7 @@ "id": "#Communication.payload:Task", "FastInvariant-ele1": {}, "cardinality": "1..11", + "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", "children": { "id": { "id": "#Communication.payload.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index a1fa255c..5b2a4e37 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -32,7 +32,7 @@ public class ValidationManifestTest /// //[Ignore] [DataTestMethod] - [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "no/Person-test-bad")] + [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "line-pattern-card-test")] public void RunSingleTest(TestCase testCase, string baseDirectory) => _runner.RunTestCase(testCase, DotNetValidator.Create(), baseDirectory); From 471fbc105e401b8bf24ecd6e653a796bb263c7eb Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 1 Oct 2024 16:38:16 +0200 Subject: [PATCH 10/25] getting closer... --- .../SchemaBuilders/TypeReferenceBuilder.cs | 10 ++++-- src/Firely.Fhir.Validation/Impl/BaseRef.cs | 35 +++++++++++++++++++ .../Impl/ChildrenValidator.cs | 17 +++++++-- .../PublicAPI.Unshipped.txt | 4 +++ .../Schema/DefinitionPath.cs | 6 ++-- .../Schema/InstancePath.cs | 2 +- .../Schema/PathStack.cs | 12 +++++-- .../SchemaSnaps/Bundle.json | 10 +++--- .../SchemaSnaps/Composition.json | 8 ++--- .../SchemaSnaps/Patient.json | 6 ++-- .../SchemaSnaps/PatternSlice.json | 8 +++-- .../SchemaSnaps/ProfiledObservation.json | 4 +-- .../SchemaSnaps/ProfiledQuestionnaire.json | 8 ++--- .../SchemaSnaps/Questionnaire.json | 8 ++--- .../SchemaSnaps/SecondaryTypeRefSlice.json | 8 ++--- .../TestData/issue-165/fhirpkg.lock.json | 2 +- .../FhirTestCases | 2 +- .../Impl/TestDefinitionPath.cs | 26 +++++++------- 18 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 src/Firely.Fhir.Validation/Impl/BaseRef.cs diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 2f29fed7..d7d765f6 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -72,6 +72,13 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv if (typeAssertion is not null) yield return typeAssertion; } + else + { + if (def.Type.SingleOrDefault()?.Code is { } tc) + { + yield return new BaseRef(tc); + } + } } private static bool shouldValidateTypeReference(ElementDefinitionNavigator nav) @@ -85,9 +92,6 @@ private static bool shouldValidateTypeReference(ElementDefinitionNavigator nav) //if there are no children, we should validate against the type reference, because that's where we find the child constraints. if (!nav.HasChildren) return true; - - //if this is a backbone element, we should validate against the base backbone element, since this validation would be skipped if we only validate the children it is extended with. - if (def.IsBackboneElement()) return true; //if there are children, we should only validate against the type reference if there are profile details. #if STU3 diff --git a/src/Firely.Fhir.Validation/Impl/BaseRef.cs b/src/Firely.Fhir.Validation/Impl/BaseRef.cs new file mode 100644 index 00000000..127b6b7e --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/BaseRef.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json.Linq; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation; + +/// +/// Not an actual assertion. Contains the type code for the element when no type reference was created. +/// +[DataContract] +[EditorBrowsable(EditorBrowsableState.Never)] +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")] +#else +[System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] +#endif +public class BaseRef : IAssertion +{ + /// + /// Create a new BaseRef. + /// + /// + public BaseRef(string baseRef) + { + this.Type = baseRef; + } + + /// + /// The type code for the element when no type reference was created. + /// + public string Type { get; } + + /// + public JToken ToJson() => new JProperty("baseRef", Type); +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index d9196731..c9278d4a 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -115,7 +115,7 @@ ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings vc, Val m.InstanceElements ?? NOELEMENTS, vc, state - .UpdateLocation(vs => vs.ToChild(m.ChildName)) + .UpdateLocation(vs => vs.ToChild(m.ChildName, m.TryExtractType())) .UpdateInstanceLocation(ip => ip.ToChild(m.ChildName, choiceElement(m))) )) ?? Enumerable.Empty()); @@ -225,5 +225,18 @@ internal record MatchResult(List? Matches, List? UnmatchedIn /// Set of elements belong to this child /// Usually, this is the set of elements with the same name and the group of assertions that represents /// the validation rule for that element generated from the StructureDefinition. - internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null); + internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null) + { + public string? TryExtractType() + { + if (Assertion is ElementSchema es) + { +#pragma warning disable CS0618 // Type or member is obsolete + return es.Members.OfType().SingleOrDefault()?.Type; +#pragma warning restore CS0618 // Type or member is obsolete + } + + return null; + } + } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index e344b4f1..2bd4baf9 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -28,6 +28,10 @@ Firely.Fhir.Validation.AnyValidator.SummaryError.get -> Firely.Fhir.Validation.I Firely.Fhir.Validation.AnyValidator.ToJson() -> Newtonsoft.Json.Linq.JToken! Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions Firely.Fhir.Validation.AssertionValidators +Firely.Fhir.Validation.BaseRef +Firely.Fhir.Validation.BaseRef.BaseRef(string! baseRef) -> void +Firely.Fhir.Validation.BaseRef.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.BaseRef.Type.get -> string! Firely.Fhir.Validation.BasicValidator Firely.Fhir.Validation.BasicValidator.BasicValidator() -> void Firely.Fhir.Validation.BindingValidator diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index a4e55a08..14488026 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -56,7 +56,9 @@ IEnumerable getPossibleElementIds(PathStackEvent? e, string current = "" return e switch { null => [current], - ChildNavEvent cne => getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}"), + ChildNavEvent cne => cne.Type is not null + ? getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}").Append($"{cne.Type}{current}") + : getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}"), CheckSliceEvent cse => getPossibleElementIds(cse.Previous, $":{cse.SliceName}{current}").Concat(getPossibleElementIds(cse.Previous, current)), InvokeProfileEvent ipe => (ipe.Previous is not null @@ -132,7 +134,7 @@ public bool TryGetSliceInfo(out string? sliceInfo) /// /// Update the path to include a move to a child element. /// - public DefinitionPath ToChild(string name) => new(new ChildNavEvent(Current, name, null)); + public DefinitionPath ToChild(string name, string? type = null) => new(new ChildNavEvent(Current, name, null, type)); /// /// Update the path to include an invocation of a (nested) profile. diff --git a/src/Firely.Fhir.Validation/Schema/InstancePath.cs b/src/Firely.Fhir.Validation/Schema/InstancePath.cs index ef14e915..4b569925 100644 --- a/src/Firely.Fhir.Validation/Schema/InstancePath.cs +++ b/src/Firely.Fhir.Validation/Schema/InstancePath.cs @@ -57,7 +57,7 @@ Current is null /// /// The name of the child element. /// The type of the choice element, if applicable. - public InstancePath ToChild(string name, string? choiceType = null) => new(new ChildNavEvent(Current, name, choiceType)); + public InstancePath ToChild(string name, string? choiceType = null) => new(new ChildNavEvent(Current, name, choiceType, null)); /// /// Update the path to include an index of a child element. diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index fd67b28f..e1255a94 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -62,17 +62,23 @@ internal abstract class PathStackEvent internal class ChildNavEvent : PathStackEvent { - public ChildNavEvent(PathStackEvent? previous, string childName, string? choiceType) : base(previous) + public ChildNavEvent(PathStackEvent? previous, string childName, string? choiceType, string? type) : base(previous) { ChildName = childName; ChoiceType = choiceType; + Type = type; } public string ChildName { get; } public string? ChoiceType { get; } + public string? Type { get; } - protected internal override string Render() => - ChoiceType is not null ? $".{ChildName[..^3]}{ChoiceType.Capitalize()}" : $".{ChildName}"; + protected internal override string Render() => this switch + { + { Type: not null } => $".{ChildName}({Type})", + { ChoiceType: not null } => $".{ChildName[..^3]}{ChoiceType.Capitalize()}", + _ => $".{ChildName}" + }; public override string ToString() => $"ChildNavEvent: ChildName: {ChildName}, ChoiceType: {ChoiceType}"; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json index d4c1682b..688968c8 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Bundle.json @@ -191,7 +191,7 @@ "humanDescription": "must be a resource unless there's a request or response" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.id", @@ -232,7 +232,7 @@ "id": "#Bundle.entry.search", "FastInvariant-ele1": {}, "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.search.id", @@ -276,7 +276,7 @@ "id": "#Bundle.entry.request", "FastInvariant-ele1": {}, "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.request.id", @@ -344,7 +344,7 @@ "id": "#Bundle.entry.response", "FastInvariant-ele1": {}, "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.response.id", @@ -408,7 +408,7 @@ "definitions": [ { "id": "#Bundle.link", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json index d198b1fb..839e8e12 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Composition.json @@ -305,7 +305,7 @@ "id": "#Composition.attester", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.attester.id", @@ -455,7 +455,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.relatesTo.id", @@ -541,7 +541,7 @@ "id": "#Composition.event", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.event.id", @@ -636,7 +636,7 @@ "definitions": [ { "id": "#Composition.section", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.section.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json index 43a4276b..5400a4c4 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Patient.json @@ -263,7 +263,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -374,7 +374,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -506,7 +506,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json index b373c700..beff1951 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json @@ -124,6 +124,7 @@ "id": "#Patient.identifier:Fixed", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -227,6 +228,7 @@ "id": "#Patient.identifier:PatternBinding", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -460,7 +462,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -550,7 +552,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -668,7 +670,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json index d98f0b0c..ac8219b6 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledObservation.json @@ -820,7 +820,7 @@ "humanDescription": "Must have at least a low or a high or text" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.referenceRange.id", @@ -1034,7 +1034,7 @@ "id": "#Observation.component", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.component.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json index fde3296d..fbfb3fcf 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/ProfiledQuestionnaire.json @@ -341,7 +341,7 @@ "humanDescription": "Can only have multiple initial values for repeating items" }, "cardinality": "1..100", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -419,7 +419,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -629,7 +629,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -751,7 +751,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json index 73356efc..f810576e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/Questionnaire.json @@ -354,7 +354,7 @@ "definitions": [ { "id": "#Questionnaire.item", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -432,7 +432,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -642,7 +642,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -764,7 +764,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json index 751819a7..a357f5b6 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json @@ -614,7 +614,7 @@ "id": "#Communication.payload", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -734,7 +734,7 @@ "id": "#Communication.payload:String", "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -794,7 +794,7 @@ "id": "#Communication.payload:DocumentReference", "FastInvariant-ele1": {}, "cardinality": "1..20", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -865,7 +865,7 @@ "id": "#Communication.payload:Task", "FastInvariant-ele1": {}, "cardinality": "1..11", - "ref": "http://hl7.org/fhir/StructureDefinition/BackboneElement", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json index ad3b2901..e784886b 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json @@ -1,5 +1,5 @@ { - "updated": "2024-09-17T11:08:44.818526+02:00", + "updated": "2024-10-01T15:03:03.686238+02:00", "dependencies": { "kbv.ita.erp": "1.0.2", "hl7.fhir.r4.core": "4.0.1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 99bf0f18..f51a07a0 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 99bf0f180abfdc87461216f94214c4387179341f +Subproject commit f51a07a09edce96c752a8340a78ca4f2feee45b9 diff --git a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs index 4e349830..73240874 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs @@ -49,13 +49,13 @@ public void NavigateInSlice() var testee = DefinitionPath.Start().InvokeSchema(fhirTypeSchema); testee.HasDefinitionChoiceInformation.Should().BeFalse(); - testee = testee.ToChild("name"); + testee = testee.ToChild("name", "HumanName"); testee.HasDefinitionChoiceInformation.Should().BeFalse(); testee = testee.CheckSlice("vv"); testee.HasDefinitionChoiceInformation.Should().BeTrue(); - testee.ToString().Should().Be("Patient.name[vv]"); + testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); } [TestMethod] @@ -65,12 +65,12 @@ public void NavigateInsideProfile() var testee = DefinitionPath .Start() .InvokeSchema(elemtSchema) - .ToChild("a") - .ToChild("b") + .ToChild("a", "string") + .ToChild("b", "string") .CheckSlice("s1") - .ToChild("c"); + .ToChild("c", "string"); - testee.ToString().Should().Be("http://test.org/test.a.b[s1].c"); + testee.ToString().Should().Be("http://test.org/test.a(string).b(string)[s1].c(string)"); } [TestMethod] @@ -83,9 +83,9 @@ public void NavigateAcrossProfile() .InvokeSchema(fhirTypeSchema) .ToChild("name") .InvokeSchema(fhirProfileSchema) - .ToChild("family"); + .ToChild("family", "string"); - testee.ToString().Should().Be("Patient.name->HumanName(http://test.org/humanname-profile).family"); + testee.ToString().Should().Be("Patient.name->HumanName(http://test.org/humanname-profile).family(string)"); } [TestMethod] @@ -94,24 +94,24 @@ public void TestGetSliceInfo() var fhirTypeSchema = new ResourceSchema(new StructureDefinitionInformation("http://test.org/resource", null, "Patient", StructureDefinitionInformation.TypeDerivationRule.Specialization, false)); var testee = DefinitionPath.Start().InvokeSchema(fhirTypeSchema); - testee = testee.ToChild("name"); + testee = testee.ToChild("name", "HumanName"); testee.TryGetSliceInfo(out _).Should().Be(false); testee = testee.CheckSlice("vv"); - testee.ToString().Should().Be("Patient.name[vv]"); + testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); string? sliceInfo; testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv"); testee = testee.CheckSlice("xx"); - testee.ToString().Should().Be("Patient.name[vv][xx]"); + testee.ToString().Should().Be("Patient.name(HumanName)[vv][xx]"); testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv, subslice xx"); - testee = testee.ToChild("family"); - testee.ToString().Should().Be("Patient.name[vv][xx].family"); + testee = testee.ToChild("family", "string"); + testee.ToString().Should().Be("Patient.name(HumanName)[vv][xx].family(string)"); testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv, subslice xx"); From e2adeed02151b100fd78739f4decad8ed5ec66dc Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 14:25:45 +0200 Subject: [PATCH 11/25] it works now! --- .../Impl/ExtensionContextValidator.cs | 4 +- src/Firely.Fhir.Validation/Impl/FhirSchema.cs | 7 ++ .../Impl/SliceValidator.cs | 12 ++- .../Schema/DefinitionPath.cs | 73 +++++++++++-------- .../Schema/PathStack.cs | 5 +- .../FhirTestCases | 2 +- .../FhirTests/ValidationManifestTest.cs | 2 +- .../Impl/TestDefinitionPath.cs | 9 ++- 8 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index b49a40ff..02accb3d 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -112,9 +112,7 @@ private static bool validateElementContext(string contextExpression, ValidationS // context with slicing: Address.extension:simpleExtension var exprHasSlicingIdentifier = contextExpression.Contains(':'); - return - defPath.GetAllPossibleElementIds().Any(eid => eid == (exprHasSlicingIdentifier ? contextExpression : contextExpression + ".extension")) || - needsElementAdded && contextExpression is "Element" or "Base"; + return defPath.MatchesContext(contextExpression); } private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) diff --git a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs index 9feff8e3..72093fe0 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs @@ -91,5 +91,12 @@ internal override IEnumerable MetadataProps() /// The canonical from the included . /// public Canonical Url => Id; + + internal IEnumerable GetOwnAndBaseTypes() + { + return (StructureDefinition.BaseCanonicals ?? Enumerable.Empty()) + .Append(StructureDefinition.Canonical) + .Select(canonical => canonical.Uri!.Split('/').Last()); + } } } diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index 5ed9f404..112a48a6 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -271,12 +271,16 @@ public void AddToSlice(SliceCase slice, IScopedNode item, int originalIndex) public void AddToDefault(IScopedNode item, int originalIndex) => _defaultBucket.Add(new(item, originalIndex)); public ResultReport[] Validate(ValidationSettings vc, ValidationState state) - => this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value))) - .Append(_defaultAssertion.ValidateMany(_defaultBucket.Select(d => d.Node), vc, forSlice(state, "@default", _defaultBucket))).ToArray(); + { + // we check for any slices' type, and use that as the type for the whole slice + var type = this.Keys.Select(sliceCase => sliceCase.Assertion).OfType().SelectMany(elemSchema => elemSchema.Members).OfType().FirstOrDefault()?.Type ?? "unknown type"; + return this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value, type))) + .Append(_defaultAssertion.ValidateMany(_defaultBucket.Select(d => d.Node), vc, forSlice(state, "@default", _defaultBucket, type))).ToArray(); + } - private static ValidationState forSlice(ValidationState current, string sliceName, IList? list) => + private static ValidationState forSlice(ValidationState current, string sliceName, IList? list, string type) => current - .UpdateLocation(vs => vs.CheckSlice(sliceName)) + .UpdateLocation(vs => vs.CheckSlice(sliceName, type)) .UpdateInstanceLocation(vs => vs.AddOriginalIndices(toOrderedList(list))); private static IEnumerable toListOfTypedElements(IList? list) => diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index 14488026..01d80d47 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -45,43 +45,54 @@ public bool HasDefinitionChoiceInformation } } - internal IEnumerable GetAllPossibleElementIds() + internal bool MatchesContext(string contextEid) { - // if the previous event was not an invoke profile event, we need to explicitly include base and element (it cannot be a resource) or a complex element - var needsElementAdded = Current?.Previous is not InvokeProfileEvent; - return getPossibleElementIds(Current).Concat(needsElementAdded ? ["Base.extension", "Element.extension"] : []); - - IEnumerable getPossibleElementIds(PathStackEvent? e, string current = "") + var match = new Regex("^(?[A-Za-z]*).?(?.*)$").Match(contextEid); + + if (!match.Success) { - return e switch + throw new InvalidOperationException($"Invalid context element id: {contextEid}"); + } + + var type = match.Groups["type"].Value; + var location = match.Groups["location"].Value; + var locationComponents = location == "" ? [] : location.Split('.', ':'); + + return matchesContext( + findExtensionContext(Current), + type, + locationComponents + ); + + static PathStackEvent? findExtensionContext(PathStackEvent? current) => + current switch { - null => [current], - ChildNavEvent cne => cne.Type is not null - ? getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}").Append($"{cne.Type}{current}") - : getPossibleElementIds(cne.Previous, $".{cne.ChildName}{current}"), - CheckSliceEvent cse => getPossibleElementIds(cse.Previous, $":{cse.SliceName}{current}").Concat(getPossibleElementIds(cse.Previous, current)), - InvokeProfileEvent ipe => - (ipe.Previous is not null - ? getPossibleElementIds(ipe.Previous, current) - : []) - .Concat - ( + null => null, + ChildNavEvent { ChildName: "extension" or "modifierExtension" } cne => current.Previous, + _ => findExtensionContext(current.Previous) + }; + } + + private static bool matchesContext(PathStackEvent? current, string type, string[] location) + { + if (location.Length == 0) + return current switch + { + ChildNavEvent cne => cne.Type == type || type == "Element", + CheckSliceEvent cse => cse.Type == type || $"{cse.Type}:{cse.SliceName}" == type || type == "Element", #pragma warning disable CS0618 // Type or member is obsolete - getOwnAndBaseTypesFromSchema((FhirSchema)ipe.Schema).Select(type => $"{type}{current}") + InvokeProfileEvent ipe => ((FhirSchema)ipe.Schema).GetOwnAndBaseTypes().Contains(type), #pragma warning restore CS0618 // Type or member is obsolete - ), - _ => throw new InvalidOperationException("Unexpected event type") + _ => false }; - } - -#pragma warning disable CS0618 // Type or member is obsolete - IEnumerable getOwnAndBaseTypesFromSchema(FhirSchema schema) + + return current switch { -#pragma warning restore CS0618 // Type or member is obsolete - return (schema.StructureDefinition.BaseCanonicals ?? Enumerable.Empty()) - .Append(schema.StructureDefinition.Canonical) - .Select(canonical => canonical.Uri!.Split('/').Last()); - } + ChildNavEvent cne => cne.ChildName == location.Last() && matchesContext(current.Previous, type, location[..^1]), + CheckSliceEvent cse => cse.SliceName == location.Last() && matchesContext(current.Previous, type, location[..^1]) || matchesContext(current.Previous, type, location), + InvokeProfileEvent => matchesContext(current.Previous, type, location), + _ => false + }; } /// @@ -144,6 +155,6 @@ public bool TryGetSliceInfo(out string? sliceInfo) /// /// Update the path to include a move into a specific slice. /// - public DefinitionPath CheckSlice(string sliceName) => new(new CheckSliceEvent(Current, sliceName)); + public DefinitionPath CheckSlice(string sliceName, string type) => new(new CheckSliceEvent(Current, sliceName, type)); } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index e1255a94..7105040c 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -75,7 +75,6 @@ public ChildNavEvent(PathStackEvent? previous, string childName, string? choiceT protected internal override string Render() => this switch { - { Type: not null } => $".{ChildName}({Type})", { ChoiceType: not null } => $".{ChildName[..^3]}{ChoiceType.Capitalize()}", _ => $".{ChildName}" }; @@ -164,12 +163,14 @@ protected internal override string Render() internal class CheckSliceEvent : PathStackEvent { - public CheckSliceEvent(PathStackEvent? previous, string sliceName) : base(previous) + public CheckSliceEvent(PathStackEvent? previous, string sliceName, string type) : base(previous) { + Type = type; SliceName = sliceName; } public string SliceName { get; } + public string Type { get; } protected internal override string Render() => $"[{SliceName}]"; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index f51a07a0..8ac8bb10 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit f51a07a09edce96c752a8340a78ca4f2feee45b9 +Subproject commit 8ac8bb10c718f5f0f5c2d5390868090c1a1a060f diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index 5b2a4e37..351651c8 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -32,7 +32,7 @@ public class ValidationManifestTest /// //[Ignore] [DataTestMethod] - [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "line-pattern-card-test")] + [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "extension-slicing-instance")] public void RunSingleTest(TestCase testCase, string baseDirectory) => _runner.RunTestCase(testCase, DotNetValidator.Create(), baseDirectory); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs index 73240874..4bb1e5a1 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs @@ -7,6 +7,7 @@ */ using FluentAssertions; +using Hl7.Fhir.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Firely.Fhir.Validation.Tests @@ -52,7 +53,7 @@ public void NavigateInSlice() testee = testee.ToChild("name", "HumanName"); testee.HasDefinitionChoiceInformation.Should().BeFalse(); - testee = testee.CheckSlice("vv"); + testee = testee.CheckSlice("vv", "HumanName"); testee.HasDefinitionChoiceInformation.Should().BeTrue(); testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); @@ -67,7 +68,7 @@ public void NavigateInsideProfile() .InvokeSchema(elemtSchema) .ToChild("a", "string") .ToChild("b", "string") - .CheckSlice("s1") + .CheckSlice("s1", "HumanName") .ToChild("c", "string"); testee.ToString().Should().Be("http://test.org/test.a(string).b(string)[s1].c(string)"); @@ -97,14 +98,14 @@ public void TestGetSliceInfo() testee = testee.ToChild("name", "HumanName"); testee.TryGetSliceInfo(out _).Should().Be(false); - testee = testee.CheckSlice("vv"); + testee = testee.CheckSlice("vv", "HumanName"); testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); string? sliceInfo; testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv"); - testee = testee.CheckSlice("xx"); + testee = testee.CheckSlice("xx", "HumanName"); testee.ToString().Should().Be("Patient.name(HumanName)[vv][xx]"); testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); From 4b2bf84c665a3874b6aa25c21939e0cfdd669af8 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 14:38:25 +0200 Subject: [PATCH 12/25] we ended up not needing these IDs --- .../SchemaBuilder.cs | 2 +- .../Impl/SliceValidator.cs | 32 ++++++------------- .../PublicAPI.Unshipped.txt | 1 - .../SchemaSnaps/PatternSlice.json | 2 -- .../SchemaSnaps/SecondaryTypeRefSlice.json | 3 -- .../SchemaSnaps/Bundle.json | 5 +++ .../SchemaSnaps/Composition.json | 4 +++ .../SchemaSnaps/Patient.json | 3 ++ .../SchemaSnaps/PatternSlice.json | 7 ++-- .../SchemaSnaps/ProfiledObservation.json | 2 ++ .../SchemaSnaps/ProfiledQuestionnaire.json | 4 +++ .../SchemaSnaps/Questionnaire.json | 4 +++ .../SchemaSnaps/SecondaryTypeRefSlice.json | 7 ++-- .../SchemaSnaps/Bundle.json | 5 +++ .../SchemaSnaps/Composition.json | 3 ++ .../SchemaSnaps/Patient.json | 3 ++ .../SchemaSnaps/PatternSlice.json | 7 ++-- .../SchemaSnaps/ProfiledEncounter.json | 5 +++ .../SchemaSnaps/ProfiledObservation.json | 3 ++ .../SchemaSnaps/ProfiledQuestionnaire.json | 4 +++ .../SchemaSnaps/Questionnaire.json | 4 +++ .../SchemaSnaps/SecondaryTypeRefSlice.json | 7 ++-- .../SchemaSnaps/Bundle.json | 5 +++ .../SchemaSnaps/Composition.json | 4 +++ .../SchemaSnaps/Patient.json | 4 +++ .../SchemaSnaps/PatternSlice.json | 8 +++-- .../SchemaSnaps/ProfiledObservation.json | 3 ++ .../SchemaSnaps/ProfiledQuestionnaire.json | 3 ++ .../SchemaSnaps/Questionnaire.json | 3 ++ .../SchemaSnaps/SecondaryTypeRefSlice.json | 7 ++-- 30 files changed, 109 insertions(+), 45 deletions(-) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index 20e52168..2e837848 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -352,7 +352,7 @@ internal IAssertion CreateSliceValidator(ElementDefinitionNavigator root) // default). IAssertion caseConstraints = discriminatorless ? ResultAssertion.SUCCESS : convertElementToSchema(schemaId, root); - sliceList.Add(new SliceValidator.SliceCase(root.Current.ElementId, sliceName ?? root.Current.ElementId, condition, caseConstraints)); + sliceList.Add(new SliceValidator.SliceCase(sliceName ?? root.Current.ElementId, condition, caseConstraints)); } } diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index 112a48a6..02344ad1 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -62,21 +62,6 @@ public class SliceCase /// [DataMember] public IAssertion Assertion { get; private set; } - - /// - /// Construct a single in a . - /// - /// - /// - /// - /// - public SliceCase(string id, string name, IAssertion condition, IAssertion? assertion) - { - Id = id ?? throw new ArgumentNullException(nameof(id)); - Name = name ?? throw new ArgumentNullException(nameof(name)); - Condition = condition ?? throw new ArgumentNullException(nameof(condition)); - Assertion = assertion ?? throw new ArgumentNullException(nameof(assertion)); - } /// /// Construct a single in a . @@ -92,17 +77,12 @@ public SliceCase(string name, IAssertion condition, IAssertion? assertion) } /// - public JToken ToJson() { - var token = new JObject( + public JToken ToJson() => + new JObject( new JProperty("name", Name), new JProperty("condition", Condition.ToJson().MakeNestedProp()), new JProperty("assertion", Assertion.ToJson().MakeNestedProp()) ); - - if(Id is not null) token.AddFirst(new JProperty("id", Id)); - - return token; - } } /// @@ -273,7 +253,13 @@ public void AddToSlice(SliceCase slice, IScopedNode item, int originalIndex) public ResultReport[] Validate(ValidationSettings vc, ValidationState state) { // we check for any slices' type, and use that as the type for the whole slice - var type = this.Keys.Select(sliceCase => sliceCase.Assertion).OfType().SelectMany(elemSchema => elemSchema.Members).OfType().FirstOrDefault()?.Type ?? "unknown type"; + var type = this.Keys + .Select(sliceCase => sliceCase.Assertion) + .OfType() + .SelectMany(elemSchema => elemSchema.Members) + .OfType() + .FirstOrDefault()?.Type ?? "unknown type"; + return this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value, type))) .Append(_defaultAssertion.ValidateMany(_defaultBucket.Select(d => d.Node), vc, forSlice(state, "@default", _defaultBucket, type))).ToArray(); } diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index 2bd4baf9..c6bec291 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -261,7 +261,6 @@ Firely.Fhir.Validation.SliceValidator.SliceCase.Assertion.get -> Firely.Fhir.Val Firely.Fhir.Validation.SliceValidator.SliceCase.Condition.get -> Firely.Fhir.Validation.IAssertion! Firely.Fhir.Validation.SliceValidator.SliceCase.Id.get -> string? Firely.Fhir.Validation.SliceValidator.SliceCase.Name.get -> string! -Firely.Fhir.Validation.SliceValidator.SliceCase.SliceCase(string! id, string! name, Firely.Fhir.Validation.IAssertion! condition, Firely.Fhir.Validation.IAssertion? assertion) -> void Firely.Fhir.Validation.SliceValidator.SliceCase.SliceCase(string! name, Firely.Fhir.Validation.IAssertion! condition, Firely.Fhir.Validation.IAssertion? assertion) -> void Firely.Fhir.Validation.SliceValidator.SliceCase.ToJson() -> Newtonsoft.Json.Linq.JToken! Firely.Fhir.Validation.SliceValidator.Slices.get -> System.Collections.Generic.IReadOnlyList! diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json index beff1951..e24647ca 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/PatternSlice.json @@ -110,7 +110,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -200,7 +199,6 @@ } }, { - "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json index a357f5b6..83813da4 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/SchemaSnaps/SecondaryTypeRefSlice.json @@ -720,7 +720,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -765,7 +764,6 @@ } }, { - "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -836,7 +834,6 @@ } }, { - "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Bundle.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Bundle.json index ca786227..b2688650 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Bundle.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Bundle.json @@ -191,6 +191,7 @@ "humanDescription": "must be a resource unless there's a request or response" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.id", @@ -231,6 +232,7 @@ "id": "#Bundle.entry.search", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.search.id", @@ -274,6 +276,7 @@ "id": "#Bundle.entry.request", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.request.id", @@ -341,6 +344,7 @@ "id": "#Bundle.entry.response", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.response.id", @@ -404,6 +408,7 @@ "definitions": [ { "id": "#Bundle.link", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Composition.json index ed0a91c9..a600afa8 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Composition.json @@ -312,6 +312,7 @@ "id": "#Composition.attester", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.attester.id", @@ -461,6 +462,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.relatesTo.id", @@ -546,6 +548,7 @@ "id": "#Composition.event", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.event.id", @@ -640,6 +643,7 @@ "definitions": [ { "id": "#Composition.section", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.section.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Patient.json index 8a1f00f5..c688f2ac 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Patient.json @@ -270,6 +270,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -380,6 +381,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -511,6 +513,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json index 7e6ec95a..6f668edd 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/PatternSlice.json @@ -117,7 +117,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -131,6 +130,7 @@ "id": "#Patient.identifier:Fixed", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -206,7 +206,6 @@ } }, { - "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { @@ -234,6 +233,7 @@ "id": "#Patient.identifier:PatternBinding", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -467,6 +467,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -556,6 +557,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -673,6 +675,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledObservation.json index db88f491..9739b70e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledObservation.json @@ -827,6 +827,7 @@ "humanDescription": "Must have at least a low or a high or text" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.referenceRange.id", @@ -1040,6 +1041,7 @@ "id": "#Observation.component", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.component.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledQuestionnaire.json index b3bb7d3f..225aecae 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/ProfiledQuestionnaire.json @@ -362,6 +362,7 @@ "humanDescription": "Can only have multiple initial values for repeating items" }, "cardinality": "1..100", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -439,6 +440,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -648,6 +650,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -769,6 +772,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Questionnaire.json index 8edcbb30..fe012a75 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/Questionnaire.json @@ -375,6 +375,7 @@ "definitions": [ { "id": "#Questionnaire.item", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -452,6 +453,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -661,6 +663,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -782,6 +785,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json index 2950a9bc..6e10e776 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4B/SchemaSnaps/SecondaryTypeRefSlice.json @@ -621,6 +621,7 @@ "id": "#Communication.payload", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -726,7 +727,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -740,6 +740,7 @@ "id": "#Communication.payload:String", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -770,7 +771,6 @@ } }, { - "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -799,6 +799,7 @@ "id": "#Communication.payload:DocumentReference", "FastInvariant-ele1": {}, "cardinality": "1..20", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -840,7 +841,6 @@ } }, { - "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { @@ -869,6 +869,7 @@ "id": "#Communication.payload:Task", "FastInvariant-ele1": {}, "cardinality": "1..11", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Bundle.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Bundle.json index 2bfec93a..9c833689 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Bundle.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Bundle.json @@ -254,6 +254,7 @@ "humanDescription": "must be a resource unless there's a request or response" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.id", @@ -294,6 +295,7 @@ "id": "#Bundle.entry.search", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.search.id", @@ -337,6 +339,7 @@ "id": "#Bundle.entry.request", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.request.id", @@ -404,6 +407,7 @@ "id": "#Bundle.entry.response", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.response.id", @@ -507,6 +511,7 @@ "definitions": [ { "id": "#Bundle.link", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json index 129ca932..0f56e434 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json @@ -352,6 +352,7 @@ "id": "#Composition.attester", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.attester.id", @@ -521,6 +522,7 @@ "id": "#Composition.event", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.event.id", @@ -602,6 +604,7 @@ "definitions": [ { "id": "#Composition.section", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.section.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json index 6d5cb35e..caede59a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json @@ -270,6 +270,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -387,6 +388,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -532,6 +534,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json index 565ca8e1..b4678703 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json @@ -110,7 +110,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Patient.identifier:Fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -124,6 +123,7 @@ "id": "#Patient.identifier:Fixed", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -199,7 +199,6 @@ } }, { - "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { @@ -227,6 +226,7 @@ "id": "#Patient.identifier:PatternBinding", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -460,6 +460,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -549,6 +550,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -666,6 +668,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledEncounter.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledEncounter.json index 36481ab1..b82b919e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledEncounter.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledEncounter.json @@ -383,6 +383,7 @@ "humanDescription": "A type cannot be provided for a patient or group participant" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Encounter.participant.id", @@ -566,6 +567,7 @@ "id": "#Encounter.reason", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Encounter.reason.id", @@ -683,6 +685,7 @@ "id": "#Encounter.diagnosis", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Encounter.diagnosis.id", @@ -792,6 +795,7 @@ "id": "#Encounter.admission", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Encounter.admission.id", @@ -953,6 +957,7 @@ "id": "#Encounter.location", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Encounter.location.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json index ff40477f..c6c29073 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json @@ -263,6 +263,7 @@ "id": "#Observation.triggeredBy", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.triggeredBy.id", @@ -1028,6 +1029,7 @@ "humanDescription": "Must have at least a low or a high or text" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.referenceRange.id", @@ -1261,6 +1263,7 @@ "id": "#Observation.component", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.component.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json index 05023009..c76cc9f2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json @@ -382,6 +382,7 @@ "humanDescription": "Can only have answerConstraint if answerOption or answerValueSet are present. (This is a warning because extensions may serve the same purpose)" }, "cardinality": "1..100", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -466,6 +467,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -697,6 +699,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -818,6 +821,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json index be36e6e5..941a846d 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json @@ -444,6 +444,7 @@ "definitions": [ { "id": "#Questionnaire.item", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -535,6 +536,7 @@ "humanDescription": "If the operator is 'exists', the value must be a boolean" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -766,6 +768,7 @@ "id": "#Questionnaire.item.answerOption", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.answerOption.id", @@ -887,6 +890,7 @@ "id": "#Questionnaire.item.initial", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.initial.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json index dff2a4e8..772aa731 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json @@ -595,6 +595,7 @@ "id": "#Communication.payload", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -700,7 +701,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Communication.payload:Attachment", "name": "Attachment", "condition": { "pathSelector": { @@ -714,6 +714,7 @@ "id": "#Communication.payload:Attachment", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -744,7 +745,6 @@ } }, { - "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -773,6 +773,7 @@ "id": "#Communication.payload:DocumentReference", "FastInvariant-ele1": {}, "cardinality": "1..20", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -814,7 +815,6 @@ } }, { - "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { @@ -843,6 +843,7 @@ "id": "#Communication.payload:Task", "FastInvariant-ele1": {}, "cardinality": "1..11", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Bundle.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Bundle.json index fc75f296..0c44df92 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Bundle.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Bundle.json @@ -182,6 +182,7 @@ "humanDescription": "must be a resource unless there's a request or response" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.id", @@ -218,6 +219,7 @@ "id": "#Bundle.entry.search", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.search.id", @@ -257,6 +259,7 @@ "id": "#Bundle.entry.request", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.request.id", @@ -320,6 +323,7 @@ "id": "#Bundle.entry.response", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.entry.response.id", @@ -379,6 +383,7 @@ "definitions": [ { "id": "#Bundle.link", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Bundle.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Composition.json index 43958dd9..05b3075e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Composition.json @@ -272,6 +272,7 @@ "id": "#Composition.attester", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.attester.id", @@ -399,6 +400,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.relatesTo.id", @@ -480,6 +482,7 @@ "id": "#Composition.event", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.event.id", @@ -570,6 +573,7 @@ "definitions": [ { "id": "#Composition.section", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Composition.section.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Patient.json index af87b1b4..2bcd1759 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Patient.json @@ -248,6 +248,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -354,6 +355,7 @@ "id": "#Patient.animal", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.animal.id", @@ -409,6 +411,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -527,6 +530,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json index ca2f20a1..c4373dc9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/PatternSlice.json @@ -94,7 +94,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Patient.identifier:fixed", "name": "Fixed", "condition": { "pathSelector": { @@ -107,6 +106,7 @@ "assertion": { "id": "#Patient.identifier:fixed", "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -174,7 +174,6 @@ } }, { - "id": "Patient.identifier:PatternBinding", "name": "PatternBinding", "condition": { "pathSelector": { @@ -201,6 +200,7 @@ "assertion": { "id": "#Patient.identifier:PatternBinding", "cardinality": "0..*", + "baseRef": "Identifier", "children": { "id": { "id": "#Patient.identifier.id", @@ -416,6 +416,7 @@ "humanDescription": "SHALL at least contain a contact's details or a reference to an organization" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.contact.id", @@ -494,6 +495,7 @@ "id": "#Patient.animal", "FastInvariant-ele1": {}, "cardinality": "0..1", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.animal.id", @@ -546,6 +548,7 @@ "id": "#Patient.communication", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.communication.id", @@ -646,6 +649,7 @@ "id": "#Patient.link", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Patient.link.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledObservation.json index ba8a8db7..e235f636 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledObservation.json @@ -674,6 +674,7 @@ "humanDescription": "Must have at least a low or a high or text" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.referenceRange.id", @@ -736,6 +737,7 @@ "id": "#Observation.related", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.related.id", @@ -824,6 +826,7 @@ "id": "#Observation.component", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Observation.component.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledQuestionnaire.json index db2d1ee6..c6c0ecc4 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/ProfiledQuestionnaire.json @@ -272,6 +272,7 @@ "humanDescription": "Group items must have nested items, display items cannot have nested items" }, "cardinality": "1..100", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -339,6 +340,7 @@ "humanDescription": "enableWhen must contain either a 'answer' or a 'hasAnswer' element" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -549,6 +551,7 @@ "id": "#Questionnaire.item.option", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.option.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Questionnaire.json index 83545d27..db695156 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/Questionnaire.json @@ -305,6 +305,7 @@ "definitions": [ { "id": "#Questionnaire.item", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.id", @@ -378,6 +379,7 @@ "humanDescription": "enableWhen must contain either a 'answer' or a 'hasAnswer' element" }, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.enableWhen.id", @@ -603,6 +605,7 @@ "id": "#Questionnaire.item.option", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Questionnaire.item.option.id", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json index 819fb465..02cb960c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/SchemaSnaps/SecondaryTypeRefSlice.json @@ -559,6 +559,7 @@ "id": "#Communication.payload", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -659,7 +660,6 @@ "defaultAtEnd": false, "case": [ { - "id": "Communication.payload:String", "name": "String", "condition": { "pathSelector": { @@ -673,6 +673,7 @@ "id": "#Communication.payload:String", "FastInvariant-ele1": {}, "cardinality": "0..*", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -698,7 +699,6 @@ } }, { - "id": "Communication.payload:DocumentReference", "name": "DocumentReference", "condition": { "allOf": { @@ -727,6 +727,7 @@ "id": "#Communication.payload:DocumentReference", "FastInvariant-ele1": {}, "cardinality": "1..20", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", @@ -763,7 +764,6 @@ } }, { - "id": "Communication.payload:Task", "name": "Task", "condition": { "allOf": { @@ -792,6 +792,7 @@ "id": "#Communication.payload:Task", "FastInvariant-ele1": {}, "cardinality": "1..11", + "baseRef": "BackboneElement", "children": { "id": { "id": "#Communication.payload.id", From c9b2a90b9f3bf1c541e532e2aa620d47656fc444 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 14:42:11 +0200 Subject: [PATCH 13/25] changed these back --- .../BasicSchemaBuilderTests.cs | 4 ++-- .../FhirTests/ValidationManifestTest.cs | 4 ++-- .../Impl/TestDefinitionPath.cs | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index 45e21f01..31a06af2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -35,8 +35,8 @@ public class BasicSchemaBuilderTests : IClassFixture public BasicSchemaBuilderTests(SchemaBuilderFixture fixture, ITestOutputHelper oh) => (_output, _fixture) = (oh, fixture); - // [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] - [Fact] + [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] + // [Fact] public void OverwriteSchemaSnaps() { compareToSchemaSnaps(true); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index 351651c8..ec4540cc 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -32,7 +32,7 @@ public class ValidationManifestTest /// //[Ignore] [DataTestMethod] - [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "extension-slicing-instance")] + [ValidationManifestDataSource(TEST_CASES_MANIFEST, singleTest: "mr-m-simple-nossystem")] public void RunSingleTest(TestCase testCase, string baseDirectory) => _runner.RunTestCase(testCase, DotNetValidator.Create(), baseDirectory); @@ -59,7 +59,7 @@ public void RunFirelySdkTests(TestCase testCase, string baseDirectory) /// - The method `ClassCleanup` will gather all the testcases and serialize those to disk. The filename can be altered in /// that method /// - // [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] + [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] [TestMethod] public void AddFirelySdkValidatorResults() => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { DotNetValidator.Create() }); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs index 4bb1e5a1..c971df87 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/TestDefinitionPath.cs @@ -56,7 +56,7 @@ public void NavigateInSlice() testee = testee.CheckSlice("vv", "HumanName"); testee.HasDefinitionChoiceInformation.Should().BeTrue(); - testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); + testee.ToString().Should().Be("Patient.name[vv]"); } [TestMethod] @@ -71,7 +71,7 @@ public void NavigateInsideProfile() .CheckSlice("s1", "HumanName") .ToChild("c", "string"); - testee.ToString().Should().Be("http://test.org/test.a(string).b(string)[s1].c(string)"); + testee.ToString().Should().Be("http://test.org/test.a.b[s1].c"); } [TestMethod] @@ -86,7 +86,7 @@ public void NavigateAcrossProfile() .InvokeSchema(fhirProfileSchema) .ToChild("family", "string"); - testee.ToString().Should().Be("Patient.name->HumanName(http://test.org/humanname-profile).family(string)"); + testee.ToString().Should().Be("Patient.name->HumanName(http://test.org/humanname-profile).family"); } [TestMethod] @@ -100,19 +100,19 @@ public void TestGetSliceInfo() testee = testee.CheckSlice("vv", "HumanName"); - testee.ToString().Should().Be("Patient.name(HumanName)[vv]"); + testee.ToString().Should().Be("Patient.name[vv]"); string? sliceInfo; testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv"); testee = testee.CheckSlice("xx", "HumanName"); - testee.ToString().Should().Be("Patient.name(HumanName)[vv][xx]"); + testee.ToString().Should().Be("Patient.name[vv][xx]"); testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv, subslice xx"); testee = testee.ToChild("family", "string"); - testee.ToString().Should().Be("Patient.name(HumanName)[vv][xx].family(string)"); + testee.ToString().Should().Be("Patient.name[vv][xx].family"); testee.TryGetSliceInfo(out sliceInfo).Should().Be(true); sliceInfo.Should().Be("vv, subslice xx"); From cc44a90cb387cf40ea3b43ff2b5d2d5785fb036d Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 15:59:19 +0200 Subject: [PATCH 14/25] it seems as though this is more correct. Changed implementation of Extension-Context.Resource --- .../Impl/ExtensionContextValidator.cs | 3 +-- .../TestData/issue-165/fhirpkg.lock.json | 2 +- .../FhirTestCases | 2 +- .../ValidationIntegrationTests.cs | 2 +- .../Impl/ExtensionContextValidatorTests.cs | 8 ++++---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index 02accb3d..1ea69723 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -93,11 +93,10 @@ private static bool validateContext(IScopedNode input, TypedContext context, Val throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); return context.Type switch { - ContextType.RESOURCE => contextNode.Location.EndsWith(context.Expression), ContextType.DATATYPE => contextNode.InstanceType == context.Expression, ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), - ContextType.ELEMENT => validateElementContext(context.Expression, state), + ContextType.ELEMENT or ContextType.RESOURCE => validateElementContext(context.Expression, state), _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") }; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json index e784886b..aad7f301 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/TestData/issue-165/fhirpkg.lock.json @@ -1,5 +1,5 @@ { - "updated": "2024-10-01T15:03:03.686238+02:00", + "updated": "2024-10-02T15:57:47.311953+02:00", "dependencies": { "kbv.ita.erp": "1.0.2", "hl7.fhir.r4.core": "4.0.1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 8ac8bb10..78ee6866 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 8ac8bb10c718f5f0f5c2d5390868090c1a1a060f +Subproject commit 78ee6866cd34cd71d95b1d51f039596fee786e34 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs index 7473c1a9..de6907a2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs @@ -52,7 +52,7 @@ private static string getSuiteDirectory(string suiteName) => [DynamicData(nameof(getTestSuites), DynamicDataSourceType.Method)] public async Task RunValidateTestSuite(string suiteName) { - var overwrite = false; + var overwrite = true; var pd = new DirectoryInfo(Path.GetFullPath(Path.Combine(getSuiteDirectory(suiteName), "data"))); var externalReferenceResolver = new FileBasedExternalReferenceResolver(pd); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs index 0cda3218..0bad43f9 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -18,7 +18,7 @@ public class ExtensionContextValidatorTests [DataTestMethod] [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "boolean", true)] [DataRow(ExtensionContextValidator.ContextType.DATATYPE, "string", false)] - [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "active[0]", true)] + [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "Patient.active", true)] [DataRow(ExtensionContextValidator.ContextType.RESOURCE, "OperationOutcome", false)] [DataRow(ExtensionContextValidator.ContextType.EXTENSION, "http://example.org/extensions#test", false)] [DataRow(ExtensionContextValidator.ContextType.ELEMENT, "boolean", false)] @@ -35,7 +35,7 @@ public void Extension_UsedInContext_ValidatesCorrectly(ExtensionContextValidator ["true"] // only look at contexts ); - AssertAgainstContextValidator(ctxValidator, expected); + assertAgainstContextValidator(ctxValidator, expected); } [DataTestMethod] @@ -50,7 +50,7 @@ public void Extension_WithContextInvariants_ValidatesCorrectly(bool expected, pa invariants ); - AssertAgainstContextValidator(validator, expected); + assertAgainstContextValidator(validator, expected); } [DataTestMethod] @@ -101,7 +101,7 @@ public void Extension_WithExtensionContext_ValidatesCorrectly(bool expected, str Assert.AreEqual(result.IsSuccessful, expected); } - private void AssertAgainstContextValidator(ExtensionContextValidator ctxValidator, bool expectedResult) + private void assertAgainstContextValidator(ExtensionContextValidator ctxValidator, bool expectedResult) { var schema = new ResourceSchema( new StructureDefinitionInformation( From 02113e608fa9f781119aea8fe69389f609bd2c7e Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:19:32 +0200 Subject: [PATCH 15/25] should be last commit, finally! --- src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs | 3 ++- .../FhirTestCases | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index 1ea69723..22b91800 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -96,7 +96,8 @@ private static bool validateContext(IScopedNode input, TypedContext context, Val ContextType.DATATYPE => contextNode.InstanceType == context.Expression, ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), - ContextType.ELEMENT or ContextType.RESOURCE => validateElementContext(context.Expression, state), + ContextType.ELEMENT => validateElementContext(context.Expression, state), + ContextType.RESOURCE => context.Expression == "*" || validateElementContext(context.Expression, state), _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") }; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 78ee6866..80058d18 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 78ee6866cd34cd71d95b1d51f039596fee786e34 +Subproject commit 80058d18c54ac3f4ab181784b334957b0a84221b From 4537303d824ecb1d418444f47d0f493a75d00721 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:19:32 +0200 Subject: [PATCH 16/25] should be last commit, finally! --- .../FhirTestCases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 80058d18..ff2cd011 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 80058d18c54ac3f4ab181784b334957b0a84221b +Subproject commit ff2cd0111581df95af00ba972817b8b1735b0bac From 4a1bc68aad45e2bd9d43eaa19b1a3b1c10e8217d Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:21:31 +0200 Subject: [PATCH 17/25] Pushed to wrong branch --- .../FhirTestCases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index ff2cd011..f7a71f48 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit ff2cd0111581df95af00ba972817b8b1735b0bac +Subproject commit f7a71f4860be4fd1ef06d0349d448ea6cf3703c4 From e61ae407d969fcb0883d1f40ec8ac7f7bb372776 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:31:43 +0200 Subject: [PATCH 18/25] this check is necessary, otherwise it could crash on an extension without 2 parents --- src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index 22b91800..0bcec917 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -94,7 +94,7 @@ private static bool validateContext(IScopedNode input, TypedContext context, Val return context.Type switch { ContextType.DATATYPE => contextNode.InstanceType == context.Expression, - ContextType.EXTENSION => (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, + ContextType.EXTENSION => contextNode.InstanceType == "Extension" && (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), ContextType.ELEMENT => validateElementContext(context.Expression, state), ContextType.RESOURCE => context.Expression == "*" || validateElementContext(context.Expression, state), From f98c31454be78cc712e71279d889c5ca2f033cc9 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:36:28 +0200 Subject: [PATCH 19/25] removed some unnecessary code --- .../Impl/ExtensionContextValidator.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index 0bcec917..aa953852 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -54,7 +54,7 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .AsResult(state); } - if (Contexts.Count > 0 && !Contexts.Any(context => validateContext(input, context, vc, state))) + if (Contexts.Count > 0 && !Contexts.Any(context => validateContext(input, context, state))) { return new IssueAssertion(Issue.CONTENT_INCORRECT_OCCURRENCE, $"Extension used outside of appropriate contexts. Expected context to be one of: {RenderExpectedContexts}") @@ -87,7 +87,7 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio return ResultReport.SUCCESS; } - private static bool validateContext(IScopedNode input, TypedContext context, ValidationSettings settings, ValidationState state) + private static bool validateContext(IScopedNode input, TypedContext context, ValidationState state) { var contextNode = input.ToScopedNode().Parent ?? throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); @@ -105,12 +105,6 @@ private static bool validateContext(IScopedNode input, TypedContext context, Val private static bool validateElementContext(string contextExpression, ValidationState state) { var defPath = state.Location.DefinitionPath; - var needsElementAdded = defPath.Current?.Previous is not InvokeProfileEvent; - - // if we have a slicing identifier, this is a bit tougher, since the slicing identifier actually defines the element itself, not its context. consider: - // context without slicing: Address - // context with slicing: Address.extension:simpleExtension - var exprHasSlicingIdentifier = contextExpression.Contains(':'); return defPath.MatchesContext(contextExpression); } From 1e7b2f32416b0575cccaa7f269f6dc8e0270558f Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:46:55 +0200 Subject: [PATCH 20/25] how does this change every time? --- .../FhirTestCases | 2 +- .../FhirTests/ValidationManifestTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index f7a71f48..9b2175b9 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit f7a71f4860be4fd1ef06d0349d448ea6cf3703c4 +Subproject commit 9b2175b9b86c1e31a73b58e7b429a364c3e20210 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index ec4540cc..94eb56c6 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -59,7 +59,7 @@ public void RunFirelySdkTests(TestCase testCase, string baseDirectory) /// - The method `ClassCleanup` will gather all the testcases and serialize those to disk. The filename can be altered in /// that method /// - [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] + // [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] [TestMethod] public void AddFirelySdkValidatorResults() => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { DotNetValidator.Create() }); From d4a0cb40dec587a19b32423d879665d841bf665a Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Wed, 2 Oct 2024 16:53:55 +0200 Subject: [PATCH 21/25] oops... --- .../FhirTests/ValidationManifestTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index 94eb56c6..ec4540cc 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -59,7 +59,7 @@ public void RunFirelySdkTests(TestCase testCase, string baseDirectory) /// - The method `ClassCleanup` will gather all the testcases and serialize those to disk. The filename can be altered in /// that method /// - // [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] + [Ignore("This test is only used to generate the Firely SDK results in the manifest. See the method for more info")] [TestMethod] public void AddFirelySdkValidatorResults() => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { DotNetValidator.Create() }); From e0d11b5996ad0fb3dd868993941d7f4c1315f199 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 8 Oct 2024 12:07:06 +0200 Subject: [PATCH 22/25] applied requested changes --- .gitignore | 2 +- .../.idea/projectSettingsUpdater.xml | 2 +- .../SchemaBuilders/ExtensionContextBuilder.cs | 6 --- .../SchemaBuilders/TypeReferenceBuilder.cs | 7 ++- .../Impl/ChildrenValidator.cs | 2 +- .../Impl/ExtensionContextValidator.cs | 50 ++++++++++--------- .../Impl/FhirPathValidator.cs | 32 +++--------- .../Impl/SliceValidator.cs | 7 +-- .../Impl/{BaseRef.cs => baseType.cs} | 6 +-- .../PublicAPI.Unshipped.txt | 8 +-- .../Schema/DefinitionPath.cs | 26 ++++++++-- .../Schema/PathStack.cs | 4 +- .../ValidationIntegrationTests.cs | 2 +- 13 files changed, 74 insertions(+), 80 deletions(-) rename src/Firely.Fhir.Validation/Impl/{BaseRef.cs => baseType.cs} (90%) diff --git a/.gitignore b/.gitignore index 4255ff93..747cbd0c 100644 --- a/.gitignore +++ b/.gitignore @@ -348,4 +348,4 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ -/.idea/.idea.Firely.Validator.API/.idea/workspace.xml +/.idea/.idea.Firely.Validator.API/.idea/ diff --git a/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml b/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml index 86cc6c63..4bb9f4d2 100644 --- a/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml +++ b/.idea/.idea.Firely.Validator.API/.idea/projectSettingsUpdater.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs index 64380439..602f8156 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/ExtensionContextBuilder.cs @@ -1,14 +1,8 @@ -using Hl7.Fhir.Language.Debugging; -using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using System.Collections.Generic; -using System.Linq; namespace Firely.Fhir.Validation.Compilation; -/// -/// -/// #pragma warning disable CS0618 // Type or member is obsolete internal class ExtensionContextBuilder : ISchemaBuilder { diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index d7d765f6..06a09e23 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -74,9 +74,12 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv } else { - if (def.Type.SingleOrDefault()?.Code is { } tc) + // If we do not validate against the type reference, + // we still need to know the type of the element for the extension context validator, + // so we include it in the schema here + if (def.Type.SingleOrDefault()?.Code is { } typeCode) { - yield return new BaseRef(tc); + yield return new baseType(typeCode); } } } diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index c9278d4a..762346d0 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -232,7 +232,7 @@ internal record Match(string ChildName, IAssertion Assertion, List? if (Assertion is ElementSchema es) { #pragma warning disable CS0618 // Type or member is obsolete - return es.Members.OfType().SingleOrDefault()?.Type; + return es.Members.OfType().SingleOrDefault()?.Type; #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index aa953852..f8ba911d 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -34,9 +34,9 @@ public ExtensionContextValidator(IEnumerable contexts, IEnumerable Invariants = invariants.ToList(); } - internal List Contexts { get; } + [DataMember] internal IReadOnlyCollection Contexts { get; } - internal List Invariants { get; } + [DataMember] internal IReadOnlyCollection Invariants { get; } /// /// Validate input against the expected context and invariants. @@ -61,30 +61,30 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio .AsResult(state); } - var failedInvariantResults = Invariants + var invariantResults = Invariants .Select(inv => runContextInvariant(input, inv, vc, state)) - .Where(res => !res.Success) .ToList(); - if (failedInvariantResults.Count != 0) - { - return ResultReport.Combine( - failedInvariantResults - .Where(fr => fr.Report is not null) - .Select(fr => fr.Report!) - .Concat( - failedInvariantResults - .Where(fr => fr.Report is null && fr.Success == false) - .Select(fr => - new IssueAssertion( - Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT, - $"Extension context failed invariant constraint {fr.Invariant}" - ).AsResult(state)) - ).ToList() - ); - } - - return ResultReport.SUCCESS; + // fast path for if all invariants are successful + if (invariantResults.All(r => r.Success)) + return ResultReport.SUCCESS; + + return ResultReport.Combine( + invariantResults.Select(res => + (res.Success, res.Report) switch + { + // If eval to false, throw an error + (false, null) => + new IssueAssertion( + Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT, + $"Extension context failed invariant constraint {res.Invariant}").AsResult(state), + // If evalutation threw an exception, return that exception + (_, { } report) => report, + // Otherwise return success + _ => ResultReport.SUCCESS + } + ).ToList() + ); } private static bool validateContext(IScopedNode input, TypedContext context, ValidationState state) @@ -94,7 +94,7 @@ private static bool validateContext(IScopedNode input, TypedContext context, Val return context.Type switch { ContextType.DATATYPE => contextNode.InstanceType == context.Expression, - ContextType.EXTENSION => contextNode.InstanceType == "Extension" && (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string ?? "None") == context.Expression, + ContextType.EXTENSION => contextNode.InstanceType == "Extension" && (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string) == context.Expression, ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), ContextType.ELEMENT => validateElementContext(context.Expression, state), ContextType.RESOURCE => context.Expression == "*" || validateElementContext(context.Expression, state), @@ -111,6 +111,8 @@ private static bool validateElementContext(string contextExpression, ValidationS private static InvariantValidator.InvariantResult runContextInvariant(IScopedNode input, string invariant, ValidationSettings vc, ValidationState state) { + // our invariant is defined with %extension, but the FhirPathValidator expects %%extension because that is our syntax for environment variables + // TODO investigate changing this in the SDK var fhirPathValidator = new FhirPathValidator("ctx-inv", invariant.Replace("%extension", "%%extension")); return fhirPathValidator.RunInvariant(input.ToScopedNode().Parent!, vc, state, ("extension", [input.ToScopedNode()])); } diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index aeded1d7..54f04a8d 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -104,33 +104,17 @@ public override JToken ToJson() } /// - internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) - { - try - { - ScopedNode node = input.ToScopedNode(); - - var context = new FhirEvaluationContext - { - TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) - }; - - var success = predicate(node, context, vc); - return new(success, null, Expression); - } - catch (Exception e) - { - return new(false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, - $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") - .AsResult(s)); - } - } + internal override InvariantResult RunInvariant(IScopedNode input, ValidationSettings vc, ValidationState s) => + RunInvariant(input.ToScopedNode(), vc, s); - internal InvariantResult RunInvariant(ScopedNode input, ValidationSettings vc, ValidationState s, params (string key, IEnumerable value)[] env) + internal InvariantResult RunInvariant(ScopedNode input, ValidationSettings vc, ValidationState s, params (string key, IEnumerable value)[] env) => + runInvariantInternal(input, vc, s, env); + + private InvariantResult runInvariantInternal(ScopedNode input, ValidationSettings vc, ValidationState s, params (string key, IEnumerable value)[] env) { try { - var context = new FhirEvaluationContext() + var context = new FhirEvaluationContext { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService), Environment = new Dictionary>(env.Select(kvp => new KeyValuePair>(kvp.key, kvp.value))) @@ -142,7 +126,7 @@ internal InvariantResult RunInvariant(ScopedNode input, ValidationSettings vc, V catch (Exception e) { return new(false, new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, - $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") + $"Evaluation of FhirPath for constraint '{Key}' failed: {e.Message}") .AsResult(s)); } } diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index 02344ad1..196f94bb 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -40,11 +40,6 @@ public class SliceValidator : IGroupValidatable [DataContract] public class SliceCase { - /// - /// Identifier for the slice. - /// - public string? Id { get; private set; } - /// /// Name of the slice. Used for diagnostic purposes. /// @@ -257,7 +252,7 @@ public ResultReport[] Validate(ValidationSettings vc, ValidationState state) .Select(sliceCase => sliceCase.Assertion) .OfType() .SelectMany(elemSchema => elemSchema.Members) - .OfType() + .OfType() .FirstOrDefault()?.Type ?? "unknown type"; return this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value, type))) diff --git a/src/Firely.Fhir.Validation/Impl/BaseRef.cs b/src/Firely.Fhir.Validation/Impl/baseType.cs similarity index 90% rename from src/Firely.Fhir.Validation/Impl/BaseRef.cs rename to src/Firely.Fhir.Validation/Impl/baseType.cs index 127b6b7e..a35811d1 100644 --- a/src/Firely.Fhir.Validation/Impl/BaseRef.cs +++ b/src/Firely.Fhir.Validation/Impl/baseType.cs @@ -14,13 +14,13 @@ namespace Firely.Fhir.Validation; #else [System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")] #endif -public class BaseRef : IAssertion +public class baseType : IAssertion { /// - /// Create a new BaseRef. + /// Create a new baseType. /// /// - public BaseRef(string baseRef) + public baseType(string baseRef) { this.Type = baseRef; } diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index c6bec291..3bae37f2 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -28,10 +28,10 @@ Firely.Fhir.Validation.AnyValidator.SummaryError.get -> Firely.Fhir.Validation.I Firely.Fhir.Validation.AnyValidator.ToJson() -> Newtonsoft.Json.Linq.JToken! Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions Firely.Fhir.Validation.AssertionValidators -Firely.Fhir.Validation.BaseRef -Firely.Fhir.Validation.BaseRef.BaseRef(string! baseRef) -> void -Firely.Fhir.Validation.BaseRef.ToJson() -> Newtonsoft.Json.Linq.JToken! -Firely.Fhir.Validation.BaseRef.Type.get -> string! +Firely.Fhir.Validation.baseType +Firely.Fhir.Validation.baseType.baseType(string! baseRef) -> void +Firely.Fhir.Validation.baseType.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.baseType.Type.get -> string! Firely.Fhir.Validation.BasicValidator Firely.Fhir.Validation.BasicValidator.BasicValidator() -> void Firely.Fhir.Validation.BindingValidator diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index 01d80d47..d4825a11 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -47,7 +47,8 @@ public bool HasDefinitionChoiceInformation internal bool MatchesContext(string contextEid) { - var match = new Regex("^(?[A-Za-z]*).?(?.*)$").Match(contextEid); + const string pattern = "^(?[A-Za-z]*).?(?.*)$"; + var match = Regex.Match(contextEid, pattern); if (!match.Success) { @@ -56,7 +57,7 @@ internal bool MatchesContext(string contextEid) var type = match.Groups["type"].Value; var location = match.Groups["location"].Value; - var locationComponents = location == "" ? [] : location.Split('.', ':'); + var locationComponents = location == String.Empty ? [] : location.Split('.', ':'); return matchesContext( findExtensionContext(Current), @@ -73,23 +74,38 @@ internal bool MatchesContext(string contextEid) }; } - private static bool matchesContext(PathStackEvent? current, string type, string[] location) + private static bool matchesContext(PathStackEvent? current, string type, ReadOnlySpan location) { if (location.Length == 0) return current switch { + // ChildNavEvent("active", "boolean") matches "boolean" and "Element". + // Note that if we are in a CNE at the end of our location, it is always an element, not a resource. ChildNavEvent cne => cne.Type == type || type == "Element", + // CheckSliceEvent("NLPostalCode", "string") matches "string", "string:NLPostalCode" and "Element". CheckSliceEvent cse => cse.Type == type || $"{cse.Type}:{cse.SliceName}" == type || type == "Element", + // InvokeProfileEvent("http://hl7.org/fhir/StructureDefinition/Patient") matches + // "Patient", "DomainResource", "Resource" and often "Base" as well, depending on the base types in the definition. #pragma warning disable CS0618 // Type or member is obsolete InvokeProfileEvent ipe => ((FhirSchema)ipe.Schema).GetOwnAndBaseTypes().Contains(type), #pragma warning restore CS0618 // Type or member is obsolete + // if our path is empty, clearly we are not in the right context. _ => false }; return current switch { - ChildNavEvent cne => cne.ChildName == location.Last() && matchesContext(current.Previous, type, location[..^1]), - CheckSliceEvent cse => cse.SliceName == location.Last() && matchesContext(current.Previous, type, location[..^1]) || matchesContext(current.Previous, type, location), + // If the current event is a child navigation event + // - check if the child name matches the current location component + // - check if the remaining location matches the previous events + ChildNavEvent cne => cne.ChildName == location[^1] && matchesContext(current.Previous, type, location[..^1]), + // if the current event is a slice check event + // - check if the location matches the slice name and match against the previous events + // - if the current location does not match the slice name, match against the full location again. We omit the slicename in this case! + // see also the comments near the base case for CheckSliceEvent + CheckSliceEvent cse => cse.SliceName == location[^1] && matchesContext(current.Previous, type, location[..^1]) || matchesContext(current.Previous, type, location), + // if the current event is an invoke profile event + // - ignore it. If this is the start of the expression, it would be handled by the base case. InvokeProfileEvent => matchesContext(current.Previous, type, location), _ => false }; diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index 7105040c..fc8f9f97 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -71,7 +71,7 @@ public ChildNavEvent(PathStackEvent? previous, string childName, string? choiceT public string ChildName { get; } public string? ChoiceType { get; } - public string? Type { get; } + internal string? Type { get; } protected internal override string Render() => this switch { @@ -170,7 +170,7 @@ public CheckSliceEvent(PathStackEvent? previous, string sliceName, string type) } public string SliceName { get; } - public string Type { get; } + internal string Type { get; } protected internal override string Render() => $"[{SliceName}]"; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs index de6907a2..7473c1a9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidationIntegrationTests.cs @@ -52,7 +52,7 @@ private static string getSuiteDirectory(string suiteName) => [DynamicData(nameof(getTestSuites), DynamicDataSourceType.Method)] public async Task RunValidateTestSuite(string suiteName) { - var overwrite = true; + var overwrite = false; var pd = new DirectoryInfo(Path.GetFullPath(Path.Combine(getSuiteDirectory(suiteName), "data"))); var externalReferenceResolver = new FileBasedExternalReferenceResolver(pd); From f866215766cfd61f6a79466bfa97b78755b58ae3 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 8 Oct 2024 12:08:37 +0200 Subject: [PATCH 23/25] public api changes --- src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index 3bae37f2..fa89ca41 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -259,7 +259,6 @@ Firely.Fhir.Validation.SliceValidator.Ordered.get -> bool Firely.Fhir.Validation.SliceValidator.SliceCase Firely.Fhir.Validation.SliceValidator.SliceCase.Assertion.get -> Firely.Fhir.Validation.IAssertion! Firely.Fhir.Validation.SliceValidator.SliceCase.Condition.get -> Firely.Fhir.Validation.IAssertion! -Firely.Fhir.Validation.SliceValidator.SliceCase.Id.get -> string? Firely.Fhir.Validation.SliceValidator.SliceCase.Name.get -> string! Firely.Fhir.Validation.SliceValidator.SliceCase.SliceCase(string! name, Firely.Fhir.Validation.IAssertion! condition, Firely.Fhir.Validation.IAssertion? assertion) -> void Firely.Fhir.Validation.SliceValidator.SliceCase.ToJson() -> Newtonsoft.Json.Linq.JToken! From 541934a1eb1039931ffdb701663acee8838f7a49 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 8 Oct 2024 12:56:11 +0200 Subject: [PATCH 24/25] changed implementation for extension type extension context because it was inconsistent. Also updated manifest. --- .../Impl/ExtensionContextValidator.cs | 6 +++--- .../FhirTestCases | 2 +- .../Impl/ExtensionContextValidatorTests.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs index f8ba911d..3c524bbb 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionContextValidator.cs @@ -90,15 +90,15 @@ public ResultReport Validate(IScopedNode input, ValidationSettings vc, Validatio private static bool validateContext(IScopedNode input, TypedContext context, ValidationState state) { var contextNode = input.ToScopedNode().Parent ?? - throw new InvalidOperationException("No context found while validating the context of an extension. Is your scoped node correct?"); + throw new InvalidOperationException("No context found while validating the context of an extension."); return context.Type switch { ContextType.DATATYPE => contextNode.InstanceType == context.Expression, - ContextType.EXTENSION => contextNode.InstanceType == "Extension" && (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string) == context.Expression, + ContextType.EXTENSION => contextNode.Parent?.InstanceType == "Extension" && (contextNode.Parent?.Children("url").SingleOrDefault()?.Value as string) == context.Expression, ContextType.FHIRPATH => contextNode.ResourceContext.IsTrue(context.Expression), ContextType.ELEMENT => validateElementContext(context.Expression, state), ContextType.RESOURCE => context.Expression == "*" || validateElementContext(context.Expression, state), - _ => throw new System.InvalidOperationException($"Unknown context type {context.Expression}") + _ => throw new InvalidOperationException($"Unknown context type {context.Expression}") }; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 9b2175b9..42a58a08 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 9b2175b9b86c1e31a73b58e7b429a364c3e20210 +Subproject commit 42a58a087ce2ac3f811ef1356af5daf796f1875a diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs index 0bad43f9..44042a8c 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ExtensionContextValidatorTests.cs @@ -94,11 +94,11 @@ public void Extension_WithExtensionContext_ValidatesCorrectly(bool expected, str var result = validator.Validate( pat .ToTypedElement(), - new ValidationSettings{}, + new ValidationSettings(), new ValidationState { Location = { DefinitionPath = DefinitionPath.Start().InvokeSchema(schema) } } ); - Assert.AreEqual(result.IsSuccessful, expected); + Assert.AreEqual(expected, result.IsSuccessful); } private void assertAgainstContextValidator(ExtensionContextValidator ctxValidator, bool expectedResult) From 761265278bbe922fcf2a44e34d2024785cc91476 Mon Sep 17 00:00:00 2001 From: Kasdejong Date: Tue, 8 Oct 2024 13:53:44 +0200 Subject: [PATCH 25/25] fixed no invariant result showing when the invariant evaluated to false --- src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs | 2 +- .../FhirTestCases | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 54f04a8d..54d53e77 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -121,7 +121,7 @@ private InvariantResult runInvariantInternal(ScopedNode input, ValidationSetting }; var success = predicate(input, context, vc); - return new(success, null); + return new(success, null, Expression); } catch (Exception e) { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index 42a58a08..bb6a7c9e 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit 42a58a087ce2ac3f811ef1356af5daf796f1875a +Subproject commit bb6a7c9e21f1bae5964efedd81b8ed39e03990d7