From 8636187005e9329e9beca233416a3f5392f1d6f6 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 24 Feb 2022 10:48:29 +0100 Subject: [PATCH 01/55] First attempt --- .../Hl7.Fhir.Specification.Tests.csproj | 2 +- .../Snapshot/SnapshotGeneratorTest.cs | 49 +++++++++++++++++++ ...Issue-1981-Patient.StructureDefinition.xml | 35 +++++++++++++ .../Snapshot/SnapshotGenerator.cs | 26 +++++++++- 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml diff --git a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj index 2b1c3bac03..8db23528ce 100644 --- a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj +++ b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj @@ -2,7 +2,7 @@ - + Hl7.Fhir.Specification.Tests diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 574c10468c..d5b5c9f34e 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7549,6 +7549,55 @@ public async T.Task ContinueMergingChildConstraintMultipleTypes(string url, stri extensionElement.Should().NotBeNull(); } + [TestMethod] + public async T.Task CardinalityOfExtension() + { + // Arrange + string parentId = "Patient.extension"; + string elementId = "Patient.extension:birthPlace"; + + var sd = await _testResolver.FindStructureDefinitionAsync("https://example.org/fhir/StructureDefinition/issue-1981-patient"); + + sd.Differential.Element.Should().HaveCount(2); + + var extensionElement = sd.Differential.Element.Single(x => x.ElementId == elementId); + + extensionElement.Min.Should().Be(0); + extensionElement.Max.Should().BeNull(); + + var snapshotGenerator = new SnapshotGenerator(_testResolver, _settings); + + snapshotGenerator.PrepareElement += delegate (object _, SnapshotElementEventArgs e) + { + e.Element.Should().NotBeNull(); + + if (e.Element.Annotation() != null) + e.Element.RemoveAnnotations(); + + e.Element.AddAnnotation(new TestAnnotation(e.BaseStructure, e.BaseElement)); + }; + + var elements = await snapshotGenerator.GenerateAsync(sd); + + snapshotGenerator.Outcome.Should().BeNull(); + + var parentElement = elements.Single(x => x.ElementId == parentId); + + // Act + var elementsExpanded = await snapshotGenerator.ExpandElementAsync(elements, parentElement); + + // Assert + extensionElement = elementsExpanded.Single(x => x.ElementId == elementId); + + extensionElement.Min.Should().Be(0); + extensionElement.Max.Should().Be("1"); + + var baseElement = extensionElement.Annotation().BaseElementDefinition; + + baseElement.Min.Should().Be(0); + baseElement.Max.Should().Be("1"); + } + private sealed class TestAnnotation { public TestAnnotation(StructureDefinition baseStructure, ElementDefinition baseElemDef) diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml new file mode 100644 index 0000000000..b0a893d2f7 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 379f57aace..2223e0655a 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -1159,8 +1159,12 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El var rebasedRootElem = (ElementDefinition)typeRootElem.DeepCopy(); rebasedRootElem.Path = diff.Path; // MV 20210727: copy cardinality from base (so do not use the cardinality of the type). See issue #1824 - rebasedRootElem.Min = diff.Current.Min; - rebasedRootElem.Max = diff.Current.Max; + //rebasedRootElem.Min = diff.Current.Min; + //rebasedRootElem.Max = diff.Current.Max; + + // NEW 20220224: + rebasedRootElem.Min = mostConstrainedMin(rebasedRootElem.Min, diff.Current.Min); + rebasedRootElem.Max = mostConstrainedMax(rebasedRootElem.Max, diff.Current.Max); // Merge the type profile root element; no need to expand children mergeElementDefinition(snap.Current, rebasedRootElem, false); @@ -1180,6 +1184,24 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El } return true; + + int? mostConstrainedMin(int? a, int? b) + { + if (a is null) return b; + if (b is null) return a; + + return Math.Max(a.Value, b.Value); + } + + string mostConstrainedMax(string a, string b) + { + if (string.IsNullOrEmpty(a) || a == "*") return b; + if (string.IsNullOrEmpty(b) || b == "*") return a; + + var left = int.Parse(a); + var right = int.Parse(b); + return Math.Min(left, right).ToString(); + } } // [WMR 20170209] HACK From 1ee8c468cd684d0d02e3d13ff48ca86807799e3c Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 24 Feb 2022 12:27:22 +0100 Subject: [PATCH 02/55] Use mergeMin and mergeMax of ElementDefnMerger for merging profile type cardinality --- .../Snapshot/SnapshotGeneratorTest.cs | 47 ++++++++++++------- .../Snapshot/ElementDefnMerger.cs | 46 +++++++++++++----- .../Snapshot/SnapshotGenerator.cs | 29 ++---------- 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index d5b5c9f34e..3f71c8fff1 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -88,28 +88,39 @@ private StructureDefinition CreateStructureDefinition(string url, params Element }; } - [TestMethod] - public void TestMergeMax() + [DataTestMethod] + [DataRow(null, "1", "1")] + [DataRow("1", null, "1")] + [DataRow("1", "1", "1")] + [DataRow("1", "*", "1")] + [DataRow("2", "*", "2")] + [DataRow("*", "*", "*")] + [DataRow("*", "2", "2")] + [DataRow("*", null, "*")] + [DataRow(null, "*", "*")] + [DataRow("3", "2", "2")] + [DataRow("2", "3", "2")] + public void TestMergeMax(string snap, string diff, string expected) { var sg = new SnapshotGenerator.ElementDefnMerger(); - test(null, "1", "1"); - test("1", null, "1"); - test("1", "1", "1"); - test("1", "*", "1"); - test("2", "*", "2"); - test("*", "*", "*"); - test("*", "2", "2"); - test("*", null, "*"); - test(null, "*", "*"); - test("3", "2", "2"); - test("2", "3", "2"); + var actual = sg.mergeMax(new FhirString(snap), new FhirString(diff)); + Assert.AreEqual(expected, actual.Value); + } - void test(string snap, string diff, string expected) - { - var actual = sg.mergeMax(new FhirString(snap), new FhirString(diff)); - Assert.AreEqual(expected, actual.Value); - } + [DataTestMethod] + [DataRow(null, null, null)] + [DataRow(null, 1, 1)] + [DataRow(1, null, 1)] + [DataRow(1, 2, 2)] + [DataRow(2, 1, 2)] + [DataRow(1, 1, 1)] + public void TestMinMax(int? snap, int? diff, int? expected) + { + var sg = new SnapshotGenerator.ElementDefnMerger(); + + var actual = sg.mergeMin(new UnsignedInt(snap), new UnsignedInt(diff)); + Assert.AreEqual(expected, actual.Value); } [TestMethod] diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs index 5a4933975c..ac6fc98cfa 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs @@ -6,14 +6,13 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Navigation; +using Hl7.Fhir.Utility; using System; using System.Collections.Generic; using System.Linq; -using Hl7.Fhir.Model; -using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Support; using System.Reflection; -using Hl7.Fhir.Utility; namespace Hl7.Fhir.Specification.Snapshot { @@ -105,7 +104,8 @@ void merge(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) // Mappings are cumulative, but keep unique on full contents snap.Mapping = mergeCollection(snap.Mapping, diff.Mapping, (a, b) => a.IsExactly(b)); - snap.MinElement = mergePrimitiveAttribute(snap.MinElement, diff.MinElement); + //snap.MinElement = mergePrimitiveAttribute(snap.MinElement, diff.MinElement); + snap.MinElement = mergeMin(snap.MinElement, diff.MinElement); //snap.MaxElement = mergePrimitiveAttribute(snap.MaxElement, diff.MaxElement); snap.MaxElement = mergeMax(snap.MaxElement, diff.MaxElement); @@ -132,7 +132,7 @@ void merge(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) snap.MinValue = mergeComplexAttribute(snap.MinValue, diff.MinValue); snap.MaxValue = mergeComplexAttribute(snap.MaxValue, diff.MaxValue); - + // [WMR 20160909] merge defaultValue and meaningWhenMissing, to handle core definitions; validator can detect invalid constraints snap.DefaultValue = mergeComplexAttribute(snap.DefaultValue, diff.DefaultValue); snap.MeaningWhenMissingElement = mergePrimitiveAttribute(snap.MeaningWhenMissingElement, diff.MeaningWhenMissingElement); @@ -224,8 +224,8 @@ T mergePrimitiveAttribute(T snap, T diff, bool allowAppend = false) where T : // [WMR 20160719] Handle snap == null // diffText = (snap.ObjectValue as string) + "\r\n" + diffText.Substring(3); var prefix = snap != null ? snap.ObjectValue as string : null; - diffText = string.IsNullOrEmpty(prefix) ? - diffText.Substring(3) + diffText = string.IsNullOrEmpty(prefix) ? + diffText.Substring(3) : prefix + "\r\n" + diffText.Substring(3); } @@ -239,6 +239,28 @@ T mergePrimitiveAttribute(T snap, T diff, bool allowAppend = false) where T : return snap; } + internal UnsignedInt mergeMin(UnsignedInt snap, UnsignedInt diff) + { + if (snap.IsNullOrEmpty() && !diff.IsNullOrEmpty()) + { + return deepCopyAndRaiseOnConstraint(diff); + } + + if (!diff.IsNullOrEmpty()) + { + var snapMin = snap.Value; + var diffMin = diff.Value; + + if (diffMin > snapMin) + { + return deepCopyAndRaiseOnConstraint(diff); + } + } + + return snap; + } + + // The diamond problem is especially painful for min/max - // most datatypes roots have a cardinality of 0..*, so // a non-repeating element referring to a profiled datatype @@ -255,7 +277,7 @@ internal FhirString mergeMax(FhirString snap, FhirString diff) // Now, diff has a numeric limit // So, if snap has no limit, take the diff - if(snap.Value == "*") + if (snap.Value == "*") return deepCopyAndRaiseOnConstraint(diff); // snap and diff both have a numeric value @@ -265,7 +287,7 @@ internal FhirString mergeMax(FhirString snap, FhirString diff) // compare them if they are both numerics return dv < sv ? deepCopyAndRaiseOnConstraint(diff) : snap; } - + // one of the two values cannot be parsed, just don't // do anything to not break it any further. return snap; @@ -278,9 +300,9 @@ internal FhirString mergeMax(FhirString snap, FhirString diff) return snap; } - private FhirString deepCopyAndRaiseOnConstraint(FhirString elt) + private T deepCopyAndRaiseOnConstraint(T elt) where T : PrimitiveType { - var result = (FhirString)elt.DeepCopy(); + var result = (T)elt.DeepCopy(); onConstraint(result); return result; } diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 2223e0655a..651695f0b8 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -1158,13 +1158,12 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El // Rebase before merging var rebasedRootElem = (ElementDefinition)typeRootElem.DeepCopy(); rebasedRootElem.Path = diff.Path; - // MV 20210727: copy cardinality from base (so do not use the cardinality of the type). See issue #1824 - //rebasedRootElem.Min = diff.Current.Min; - //rebasedRootElem.Max = diff.Current.Max; - // NEW 20220224: - rebasedRootElem.Min = mostConstrainedMin(rebasedRootElem.Min, diff.Current.Min); - rebasedRootElem.Max = mostConstrainedMax(rebasedRootElem.Max, diff.Current.Max); + var sg = new ElementDefnMerger(); + // merge the min of the external profile type with the diff. Most constrained wins (the maximum of both values) + rebasedRootElem.MinElement = sg.mergeMin(rebasedRootElem.MinElement, diff.Current.MinElement); + // merge the min of the external profile type with the diff. Most constrained wins (the minumum of both values) + rebasedRootElem.MaxElement = sg.mergeMax(rebasedRootElem.MaxElement, diff.Current.MaxElement); // Merge the type profile root element; no need to expand children mergeElementDefinition(snap.Current, rebasedRootElem, false); @@ -1184,24 +1183,6 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El } return true; - - int? mostConstrainedMin(int? a, int? b) - { - if (a is null) return b; - if (b is null) return a; - - return Math.Max(a.Value, b.Value); - } - - string mostConstrainedMax(string a, string b) - { - if (string.IsNullOrEmpty(a) || a == "*") return b; - if (string.IsNullOrEmpty(b) || b == "*") return a; - - var left = int.Parse(a); - var right = int.Parse(b); - return Math.Min(left, right).ToString(); - } } // [WMR 20170209] HACK From 0756c01ff6cd6fbd10995d2b07cf04f3db0492b7 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 24 Feb 2022 13:14:12 +0100 Subject: [PATCH 03/55] Adding extra comments --- .../Specification/Snapshot/ElementDefnMerger.cs | 10 +++++++++- .../Specification/Snapshot/SnapshotGenerator.cs | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs index ac6fc98cfa..0d981222b0 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs @@ -239,15 +239,23 @@ T mergePrimitiveAttribute(T snap, T diff, bool allowAppend = false) where T : return snap; } + /// + /// Merge the Min element of the differential into the snapshot. The most contrained will win: so the maximum of both values. + /// + /// + /// + /// internal UnsignedInt mergeMin(UnsignedInt snap, UnsignedInt diff) { if (snap.IsNullOrEmpty() && !diff.IsNullOrEmpty()) { + // no snap element, but diff element: return the diff: return deepCopyAndRaiseOnConstraint(diff); } if (!diff.IsNullOrEmpty()) { + // a snap element and diff element exist var snapMin = snap.Value; var diffMin = diff.Value; @@ -256,7 +264,7 @@ internal UnsignedInt mergeMin(UnsignedInt snap, UnsignedInt diff) return deepCopyAndRaiseOnConstraint(diff); } } - + // in all other cases, return the snap return snap; } diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 651695f0b8..9f4cc848a0 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -1159,6 +1159,10 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El var rebasedRootElem = (ElementDefinition)typeRootElem.DeepCopy(); rebasedRootElem.Path = diff.Path; + // MV 20220224: we have here actually a 3-way merge of the cardinality: merge of the snapshot in progress (1), differential (2) and + // the external profile type (3). We do first a merge with the profile type and the differential and then the differential + // will be merged into the snapshot. + var sg = new ElementDefnMerger(); // merge the min of the external profile type with the diff. Most constrained wins (the maximum of both values) rebasedRootElem.MinElement = sg.mergeMin(rebasedRootElem.MinElement, diff.Current.MinElement); From cf086ffc6d4c02780c76a6fc3cc6dd1d033faa55 Mon Sep 17 00:00:00 2001 From: Rob Langezaal Date: Wed, 2 Mar 2022 13:49:33 +0100 Subject: [PATCH 04/55] Removed 3-way merge of the cardinality in snapshot generator as the use of mergeMin and MergeMax in ElementDefnMerger already fixes the issue. Added constrainMax (not used) and unit tests. Updated snapshot generator unit tests. --- .../Snapshot/SnapshotGeneratorTest.cs | 124 ++++++++++++++++-- ...Issue-1981-Patient.StructureDefinition.xml | 9 +- .../TestExtension01.StructureDefinition.xml | 24 ++++ ...TestExtension0star.StructureDefinition.xml | 23 ++++ .../TestExtension11.StructureDefinition.xml | 25 ++++ ...TestExtension1star.StructureDefinition.xml | 24 ++++ .../Snapshot/ElementDefnMerger.cs | 30 ++++- .../Snapshot/SnapshotGenerator.cs | 10 -- 8 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension01.StructureDefinition.xml create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension0star.StructureDefinition.xml create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension11.StructureDefinition.xml create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension1star.StructureDefinition.xml diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 3f71c8fff1..a4c6262c60 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -89,6 +89,7 @@ private StructureDefinition CreateStructureDefinition(string url, params Element } [DataTestMethod] + [DataRow(null, null, null)] [DataRow(null, "1", "1")] [DataRow("1", null, "1")] [DataRow("1", "1", "1")] @@ -108,6 +109,33 @@ public void TestMergeMax(string snap, string diff, string expected) Assert.AreEqual(expected, actual.Value); } + [DataTestMethod] + [DataRow(null, null, null)] + [DataRow(null, "1", "1")] + [DataRow(null, "2", "2")] + [DataRow(null, "3", "3")] + [DataRow(null, "*", "*")] + [DataRow(0, null, "0")] + [DataRow(0, "1", "1")] + [DataRow(0, "2", "2")] + [DataRow(0, "3", "3")] + [DataRow(0, "*", "*")] + [DataRow(2, null, "2")] + [DataRow(2, "1", "2")] + [DataRow(2, "2", "2")] + [DataRow(2, "3", "3")] + [DataRow(2, "*", "*")] + [DataRow(4, null, "4")] + [DataRow(4, "1", "4")] + [DataRow(4, "2", "4")] + [DataRow(4, "3", "4")] + [DataRow(4, "*", "*")] + public void TestConstrainMax(int? snapMin, string snapMax, string expected) + { + var actual = SnapshotGenerator.ElementDefnMerger.constrainMax(new FhirString(snapMax), new UnsignedInt(snapMin)); + Assert.AreEqual(expected, actual.Value); + } + [DataTestMethod] [DataRow(null, null, null)] [DataRow(null, 1, 1)] @@ -115,7 +143,7 @@ public void TestMergeMax(string snap, string diff, string expected) [DataRow(1, 2, 2)] [DataRow(2, 1, 2)] [DataRow(1, 1, 1)] - public void TestMinMax(int? snap, int? diff, int? expected) + public void TestMergeMin(int? snap, int? diff, int? expected) { var sg = new SnapshotGenerator.ElementDefnMerger(); @@ -7560,21 +7588,97 @@ public async T.Task ContinueMergingChildConstraintMultipleTypes(string url, stri extensionElement.Should().NotBeNull(); } + /// + /// Test cases that have non corrected values: + /// [N1] Max lt Min + /// Test cases that have corrected values: + /// [C1] Max diff: * -> take Max snap: 1 + /// [C2] Min diff lte snap -> take Min snap: 1 + /// [TestMethod] - public async T.Task CardinalityOfExtension() + [DataRow("TestExtension01", null, null, 0, "1", 0, "1")] + [DataRow("TestExtension01", null, "0", 0, "0", 0, "1")] + [DataRow("TestExtension01", null, "1", 0, "1", 0, "1")] + [DataRow("TestExtension01", null, "*", 0, "1", 0, "1")] // [C1] + [DataRow("TestExtension01", 0, null, 0, "1", 0, "1")] + [DataRow("TestExtension01", 0, "0", 0, "0", 0, "1")] + [DataRow("TestExtension01", 0, "1", 0, "1", 0, "1")] + [DataRow("TestExtension01", 0, "*", 0, "1", 0, "1")] // [C1] + [DataRow("TestExtension01", 1, null, 1, "1", 0, "1")] + [DataRow("TestExtension01", 1, "0", 1, "0", 0, "1")] // [N1] + [DataRow("TestExtension01", 1, "1", 1, "1", 0, "1")] + [DataRow("TestExtension01", 1, "*", 1, "1", 0, "1")] // [C1] + + [DataRow("TestExtension11", null, null, 1, "1", 1, "1")] + [DataRow("TestExtension11", null, "0", 1, "0", 1, "1")] // [N1] + [DataRow("TestExtension11", null, "1", 1, "1", 1, "1")] + [DataRow("TestExtension11", null, "*", 1, "1", 1, "1")] // [C1] + [DataRow("TestExtension11", 0, null, 1, "1", 1, "1")] // [C2] + [DataRow("TestExtension11", 0, "0", 1, "0", 1, "1")] // [C2][N1] + [DataRow("TestExtension11", 0, "1", 1, "1", 1, "1")] // [C2] + [DataRow("TestExtension11", 0, "*", 1, "1", 1, "1")] // [C2][C1] + [DataRow("TestExtension11", 1, null, 1, "1", 1, "1")] + [DataRow("TestExtension11", 1, "0", 1, "0", 1, "1")] // [N1] + [DataRow("TestExtension11", 1, "1", 1, "1", 1, "1")] + [DataRow("TestExtension11", 1, "*", 1, "1", 1, "1")] // [C1] + + [DataRow("TestExtension0star", null, null, 0, "*", 0, "*")] + [DataRow("TestExtension0star", null, "0", 0, "0", 0, "*")] + [DataRow("TestExtension0star", null, "1", 0, "1", 0, "*")] + [DataRow("TestExtension0star", null, "2", 0, "2", 0, "*")] + [DataRow("TestExtension0star", null, "*", 0, "*", 0, "*")] + [DataRow("TestExtension0star", 0, null, 0, "*", 0, "*")] + [DataRow("TestExtension0star", 0, "0", 0, "0", 0, "*")] + [DataRow("TestExtension0star", 0, "1", 0, "1", 0, "*")] + [DataRow("TestExtension0star", 0, "2", 0, "2", 0, "*")] + [DataRow("TestExtension0star", 0, "*", 0, "*", 0, "*")] + [DataRow("TestExtension0star", 1, null, 1, "*", 0, "*")] + [DataRow("TestExtension0star", 1, "0", 1, "0", 0, "*")] // [N1] + [DataRow("TestExtension0star", 1, "1", 1, "1", 0, "*")] + [DataRow("TestExtension0star", 1, "2", 1, "2", 0, "*")] + [DataRow("TestExtension0star", 1, "*", 1, "*", 0, "*")] + [DataRow("TestExtension0star", 2, null, 2, "*", 0, "*")] + [DataRow("TestExtension0star", 2, "0", 2, "0", 0, "*")] // [N1] + [DataRow("TestExtension0star", 2, "1", 2, "1", 0, "*")] // [N1] + [DataRow("TestExtension0star", 2, "2", 2, "2", 0, "*")] + [DataRow("TestExtension0star", 2, "*", 2, "*", 0, "*")] + + [DataRow("TestExtension1star", null, null, 1, "*", 1, "*")] + [DataRow("TestExtension1star", null, "0", 1, "0", 1, "*")] // [N1] + [DataRow("TestExtension1star", null, "1", 1, "1", 1, "*")] + [DataRow("TestExtension1star", null, "2", 1, "2", 1, "*")] + [DataRow("TestExtension1star", null, "*", 1, "*", 1, "*")] + [DataRow("TestExtension1star", 0, null, 1, "*", 1, "*")] // [C2] + [DataRow("TestExtension1star", 0, "0", 1, "0", 1, "*")] // [C2][N1] + [DataRow("TestExtension1star", 0, "1", 1, "1", 1, "*")] // [C2] + [DataRow("TestExtension1star", 0, "2", 1, "2", 1, "*")] // [C2] + [DataRow("TestExtension1star", 0, "*", 1, "*", 1, "*")] // [C2] + [DataRow("TestExtension1star", 1, null, 1, "*", 1, "*")] + [DataRow("TestExtension1star", 1, "0", 1, "0", 1, "*")] // [N1] + [DataRow("TestExtension1star", 1, "1", 1, "1", 1, "*")] + [DataRow("TestExtension1star", 1, "2", 1, "2", 1, "*")] + [DataRow("TestExtension1star", 1, "*", 1, "*", 1, "*")] + [DataRow("TestExtension1star", 2, null, 2, "*", 1, "*")] + [DataRow("TestExtension1star", 2, "0", 2, "0", 1, "*")] // [N1] + [DataRow("TestExtension1star", 2, "1", 2, "1", 1, "*")] // [N1] + [DataRow("TestExtension1star", 2, "2", 2, "2", 1, "*")] + [DataRow("TestExtension1star", 2, "*", 2, "*", 1, "*")] + public async T.Task CardinalityOfExtension(string extension, int? diffMin, string diffMax, int extMin, string extMax, int baseMin, string baseMax) { // Arrange + string url = $"https://example.org/fhir/StructureDefinition/issue-1981-patient"; string parentId = "Patient.extension"; - string elementId = "Patient.extension:birthPlace"; + string elementId = "Patient.extension:test"; - var sd = await _testResolver.FindStructureDefinitionAsync("https://example.org/fhir/StructureDefinition/issue-1981-patient"); + var sd = await _testResolver.FindStructureDefinitionAsync(url); sd.Differential.Element.Should().HaveCount(2); var extensionElement = sd.Differential.Element.Single(x => x.ElementId == elementId); - extensionElement.Min.Should().Be(0); - extensionElement.Max.Should().BeNull(); + extensionElement.Min = diffMin; + extensionElement.Max = diffMax; + extensionElement.Type[0].ProfileElement = new FhirUri($"https://example.org/fhir/StructureDefinition/{extension}"); var snapshotGenerator = new SnapshotGenerator(_testResolver, _settings); @@ -7600,13 +7704,13 @@ public async T.Task CardinalityOfExtension() // Assert extensionElement = elementsExpanded.Single(x => x.ElementId == elementId); - extensionElement.Min.Should().Be(0); - extensionElement.Max.Should().Be("1"); + extensionElement.Min.Should().Be(extMin); + extensionElement.Max.Should().Be(extMax); var baseElement = extensionElement.Annotation().BaseElementDefinition; - baseElement.Min.Should().Be(0); - baseElement.Max.Should().Be("1"); + baseElement.Min.Should().Be(baseMin); + baseElement.Max.Should().Be(baseMax); } private sealed class TestAnnotation diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml index b0a893d2f7..efeb03b642 100644 --- a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/Issue-1981-Patient.StructureDefinition.xml @@ -3,7 +3,7 @@ - + @@ -21,13 +21,14 @@ - + - + + - + diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension01.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension01.StructureDefinition.xml new file mode 100644 index 0000000000..9d47e4c537 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension01.StructureDefinition.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension0star.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension0star.StructureDefinition.xml new file mode 100644 index 0000000000..e2e6ec02a8 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension0star.StructureDefinition.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension11.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension11.StructureDefinition.xml new file mode 100644 index 0000000000..c9d4325a1b --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension11.StructureDefinition.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension1star.StructureDefinition.xml b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension1star.StructureDefinition.xml new file mode 100644 index 0000000000..fb2d95a601 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/snapshot-test/Issue-1981/TestExtension1star.StructureDefinition.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs index 0d981222b0..be88223650 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs @@ -104,9 +104,8 @@ void merge(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) // Mappings are cumulative, but keep unique on full contents snap.Mapping = mergeCollection(snap.Mapping, diff.Mapping, (a, b) => a.IsExactly(b)); - //snap.MinElement = mergePrimitiveAttribute(snap.MinElement, diff.MinElement); + // Note that max is not corrected when max < min! constrainMax could be used if that is desired. snap.MinElement = mergeMin(snap.MinElement, diff.MinElement); - //snap.MaxElement = mergePrimitiveAttribute(snap.MaxElement, diff.MaxElement); snap.MaxElement = mergeMax(snap.MaxElement, diff.MaxElement); // snap.base should already be there, and is not changed by the diff @@ -240,7 +239,7 @@ T mergePrimitiveAttribute(T snap, T diff, bool allowAppend = false) where T : } /// - /// Merge the Min element of the differential into the snapshot. The most contrained will win: so the maximum of both values. + /// Merge the Min element of the differential into the snapshot. The most constrained will win: so the maximum of both values. /// /// /// @@ -300,12 +299,31 @@ internal FhirString mergeMax(FhirString snap, FhirString diff) // do anything to not break it any further. return snap; } - else if (!diff.IsNullOrEmpty() && (snap.IsNullOrEmpty() || !diff.IsExactly(snap))) + + if (!diff.IsNullOrEmpty() && (snap.IsNullOrEmpty() || !diff.IsExactly(snap))) { return deepCopyAndRaiseOnConstraint(diff); } - else - return snap; + + return snap; + } + + /// + /// Make sure max >= min + /// + internal static FhirString constrainMax(FhirString max, UnsignedInt minValue) + { + if (minValue.IsNullOrEmpty()) + return max; + + // Max does not have a value but min does so take min + if (max.IsNullOrEmpty()) + return new FhirString(minValue.Value.ToString()); + + // If max is an int and is smaller than min then take min otherwise take max + return int.TryParse(max.Value, out var maxValue) && maxValue < minValue.Value + ? new FhirString(minValue.Value.ToString()) + : max; } private T deepCopyAndRaiseOnConstraint(T elt) where T : PrimitiveType diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 9f4cc848a0..e61dc5aecb 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -1159,16 +1159,6 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El var rebasedRootElem = (ElementDefinition)typeRootElem.DeepCopy(); rebasedRootElem.Path = diff.Path; - // MV 20220224: we have here actually a 3-way merge of the cardinality: merge of the snapshot in progress (1), differential (2) and - // the external profile type (3). We do first a merge with the profile type and the differential and then the differential - // will be merged into the snapshot. - - var sg = new ElementDefnMerger(); - // merge the min of the external profile type with the diff. Most constrained wins (the maximum of both values) - rebasedRootElem.MinElement = sg.mergeMin(rebasedRootElem.MinElement, diff.Current.MinElement); - // merge the min of the external profile type with the diff. Most constrained wins (the minumum of both values) - rebasedRootElem.MaxElement = sg.mergeMax(rebasedRootElem.MaxElement, diff.Current.MaxElement); - // Merge the type profile root element; no need to expand children mergeElementDefinition(snap.Current, rebasedRootElem, false); } From 2cb1cd201f64ab02eaf31d7db4508dc9f0d4e14e Mon Sep 17 00:00:00 2001 From: Rob Langezaal Date: Wed, 2 Mar 2022 16:08:13 +0100 Subject: [PATCH 05/55] Updated comments for constrainMax. --- .../Specification/Snapshot/ElementDefnMerger.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs index be88223650..f304a62ae1 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementDefnMerger.cs @@ -309,7 +309,12 @@ internal FhirString mergeMax(FhirString snap, FhirString diff) } /// - /// Make sure max >= min + /// Make sure max >= min. To be used in conjunction with mergeMax: + /// + /// snap.MaxElement = constrainMax(mergeMax(snap.MaxElement, diff.MaxElement), snap.MinElement); + /// + /// However this method is not used at the moment. + /// It may be used in the future when the need arises. /// internal static FhirString constrainMax(FhirString max, UnsignedInt minValue) { From 8621e748f861ccccc9bdcf48840ce7710d5c299e Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Fri, 4 Mar 2022 13:38:20 +0100 Subject: [PATCH 06/55] bumped version to 3.8.1 --- common | 2 +- src/firely-net-sdk.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common b/common index 7c6f7fb726..c087b43baf 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 7c6f7fb726f41f5a6645a9bc9239af9ca4ec9f82 +Subproject commit c087b43baf5e60a4a4d579a5e0bac663faf4f146 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index d1b464dd74..a7ff6e58f8 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -3,7 +3,7 @@ 3.8.1 - alpha + Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2021 Firely. Contains materials (C) HL7 International From 0d9eaf4a5ee577fe2db41597615d204648992435 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Fri, 4 Mar 2022 14:59:05 +0100 Subject: [PATCH 07/55] Start new development phase: version 3.8.2-alpha --- common | 2 +- src/firely-net-sdk.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common b/common index c087b43baf..ba31e20d00 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit c087b43baf5e60a4a4d579a5e0bac663faf4f146 +Subproject commit ba31e20d00dd0f2c5e0d56ceefa4fff4d686b309 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index a7ff6e58f8..8fab322cad 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -2,8 +2,8 @@ - 3.8.1 - + 3.8.2 + alpha Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2021 Firely. Contains materials (C) HL7 International From 76f22f658f2c4d83ea83fcf0268ef9d35a9d2c71 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Fri, 25 Mar 2022 15:39:28 +0100 Subject: [PATCH 08/55] Added state to keep track of which (nested/contained)resources have already been validated. Avoiding duplicate validation will keep bundle validation perf reasonable (we hope). --- common | 2 +- ...ructureDefinitionSerializationInfoTests.cs | 4 +- .../Validation/BasicValidationTests.cs | 21 +--- .../Validation/SliceValidationTests.cs | 2 +- .../TargetProfileValidationTests.cs | 98 +++++++++++++++++ .../Validation/TestProfileArtifactSource.cs | 28 ++++- .../Validation/VisitResolver.cs | 32 ++++++ .../ChildConstraintValidationExtensions.cs | 10 +- .../Validation/Slicing/BaseBucket.cs | 6 +- .../Slicing/BindingDiscriminator.cs | 5 +- .../Validation/Slicing/BucketFactory.cs | 14 +-- .../Slicing/CombinedDiscriminator.cs | 2 +- .../Slicing/ComprehensiveDiscriminator.cs | 2 +- .../Validation/Slicing/ConstraintsBucket.cs | 13 ++- .../Validation/Slicing/DiscriminatorBucket.cs | 11 +- .../Validation/Slicing/ElementBucket.cs | 8 +- .../Validation/Slicing/ExistsDiscriminator.cs | 2 +- .../Validation/Slicing/IDiscriminator.cs | 2 +- .../Slicing/PathBasedDiscriminator.cs | 7 +- .../Slicing/PatternDiscriminator.cs | 2 +- .../Slicing/ProfileDiscriminator.cs | 12 ++- .../Validation/Slicing/TypeDiscriminator.cs | 2 +- .../Validation/Slicing/ValueDiscriminator.cs | 2 +- .../Validation/TypeRefValidationExtensions.cs | 45 +++++--- .../Validation/ValidationLogger.cs | 81 ++++++++++++++ .../Validation/ValidationState.cs | 66 ++++++++++++ .../Validation/Validator.cs | 101 ++++++++++++------ 27 files changed, 464 insertions(+), 116 deletions(-) create mode 100644 src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs create mode 100644 src/Hl7.Fhir.Specification.Tests/Validation/VisitResolver.cs create mode 100644 src/Hl7.Fhir.Specification/Validation/ValidationLogger.cs create mode 100644 src/Hl7.Fhir.Specification/Validation/ValidationState.cs diff --git a/common b/common index ba31e20d00..732e09ab87 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit ba31e20d00dd0f2c5e0d56ceefa4fff4d686b309 +Subproject commit 732e09ab8723f9e2c6075b1adf541f220a6eabb9 diff --git a/src/Hl7.Fhir.Specification.Tests/StructureDefinitionSerializationInfoTests.cs b/src/Hl7.Fhir.Specification.Tests/StructureDefinitionSerializationInfoTests.cs index 697e9dee72..4808357e34 100644 --- a/src/Hl7.Fhir.Specification.Tests/StructureDefinitionSerializationInfoTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/StructureDefinitionSerializationInfoTests.cs @@ -1,6 +1,6 @@ using Hl7.Fhir.Specification; using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Validation; +using Hl7.Fhir.Specification.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Hl7.Fhir.Serialization.Tests @@ -18,7 +18,7 @@ public static void SetupSource(TestContext t) ); } - static IResourceResolver source = null; + private static IResourceResolver source = null; [TestMethod] public void TestCanLocateTypes() => SerializationInfoTestHelpers.TestCanLocateTypes(new StructureDefinitionSummaryProvider(source)); diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index f07cfea374..a87481b735 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -22,7 +22,7 @@ namespace Hl7.Fhir.Specification.Tests { [Trait("Category", "Validation")] - public class BasicValidationTests : IClassFixture + public partial class BasicValidationTests : IClassFixture { private readonly IResourceResolver _source; private readonly IAsyncResourceResolver _asyncSource; @@ -1270,25 +1270,6 @@ public void ValidateWithTargetProfileAndChildDefinitions() } - class VisitResolver : IResourceResolver - { - private List _visits = new List(); - - public Resource ResolveByCanonicalUri(string uri) - { - _visits.Add(uri); - return null; - } - - public Resource ResolveByUri(string uri) - { - _visits.Add(uri); - return null; - } - - internal bool Visited(string uri) => _visits.Contains(uri); - } - private class ClearSnapshotResolver : IResourceResolver { private readonly IResourceResolver _resolver; diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/SliceValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/SliceValidationTests.cs index cfef7b0bef..9f37170f83 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/SliceValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/SliceValidationTests.cs @@ -39,7 +39,7 @@ private IBucket createSliceDefs(string url, string path) //var xml = FhirSerializer.SerializeResourceToXml(sd); //File.WriteAllText(@"c:\temp\sdout.xml", xml); - return BucketFactory.CreateRoot(nav, _resolver, _validator); + return BucketFactory.CreateRoot(nav, _resolver, _validator, new ValidationState()); } /* diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs new file mode 100644 index 0000000000..9d9811898a --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs @@ -0,0 +1,98 @@ +using FluentAssertions; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Source; +using Hl7.Fhir.Utility; +using Hl7.Fhir.Validation; +using System.Collections.Generic; +using Xunit; + +namespace Hl7.Fhir.Specification.Tests +{ + [Trait("Category", "Validation")] + public class TargetProfileValidationTests + { + private class TestResolver : IResourceResolver + { + public static string Url = "http://test.org/fhir/Organization/3141"; + + private static Organization dummy = new() { Id = "3141", Name = "Dummy" }; + + public Resource ResolveByCanonicalUri(string uri) => ResolveByUri(uri); + public Resource ResolveByUri(string uri) => + uri == Url ? dummy : null; + } + + + [Fact] + public void AvoidsRedoingProfileValidation() + { + var all = new Bundle() { Type = Bundle.BundleType.Batch }; + var org1 = new Organization() { Id = "org1", Name = "Organization 1" }; + var org2 = new Organization() { Id = "org2", Name = "Organization 2", Meta = new() { Profile = new[] { TestProfileArtifactSource.PROFILED_ORG_URL } } }; + + all.Entry.Add(new() { FullUrl = refr("org1"), Resource = org1 }); + all.Entry.Add(new() { FullUrl = refr("org2"), Resource = org2 }); + + var bothRef = new List() + { + new ResourceReference("#refme"), + new ResourceReference(refr("org1")), + new ResourceReference(refr("org2")), + new ResourceReference("http://test.org/fhir/Organization/3141") + }; + + var pat1 = new Patient() + { + Meta = new() { Profile = new[] { "http://validationtest.org/fhir/StructureDefinition/PatientWithReferences" } }, + Id = "pat1", + GeneralPractitioner = bothRef, + ManagingOrganization = new(refr("org1")), + BirthDate = new("2011crap") + }; + + pat1.Contained.Add(new Organization { Id = "refme", Name = "referred" }); + + var pat2 = new Patient() + { + Meta = new() { Profile = new[] { "http://validationtest.org/fhir/StructureDefinition/PatientWithReferences" } }, + Id = "pat2", + GeneralPractitioner = bothRef, + ManagingOrganization = new(refr("org2")) + }; + + pat2.Contained.Add(new Organization { Id = "refme", Name = "referred" }); + + all.Entry.Add(new() { FullUrl = refr("pat1"), Resource = pat1 }); + all.Entry.Add(new() { FullUrl = refr("pat2"), Resource = pat2 }); + + var visitResolver = new TestResolver(); + + var cr = new SnapshotSource( + new CachedResolver( + new MultiResolver( + new TestProfileArtifactSource(), + new ZipSource("specification.zip"), + visitResolver))); + + var validatorSettings = new ValidationSettings + { + GenerateSnapshot = true, + ResourceResolver = cr, + ResolveExternalReferences = true + }; + var validator = new Validator(validatorSettings); + + var result = validator.Validate(all); + result.Success.Should().Be(false); + result.Errors.Should().Be(1); + result.ToString().Should().Contain("does not match regex"); + + var validationState = result.Annotation(); + + validationState.Global.ResourcesValidated.Value.Should().Be(16); + validationState.Instance.InternalValidations.Count.Should().Be(8); + + static string refr(string x) => "http://test.org/fhir/" + x; + } + } +} diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/TestProfileArtifactSource.cs b/src/Hl7.Fhir.Specification.Tests/Validation/TestProfileArtifactSource.cs index f76ceaa239..8a2605568c 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/TestProfileArtifactSource.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/TestProfileArtifactSource.cs @@ -5,8 +5,17 @@ using System.Collections.Generic; using System.Linq; -namespace Hl7.Fhir.Validation +namespace Hl7.Fhir.Specification.Tests { + internal static class LExt + { + public static List AddM(this List me, IEnumerable them) + { + me.AddRange(them); + return me; + } + } + internal class TestProfileArtifactSource : IResourceResolver { public List TestProfiles = new List @@ -45,7 +54,7 @@ internal class TestProfileArtifactSource : IResourceResolver buildPatientWithDeceasedConstraints(), buildBoolean(), buildMyExtension() - }; + }.AddM(buildPatientWithProfiledReferences()); private static StructureDefinition buildObservationWithTargetProfilesAndChildDefs() { @@ -458,6 +467,7 @@ private static StructureDefinition buildParametersWithBoundParams() } private const string QUANTITY_WITH_UNLIMITED_ROOT_CARDINALITY_CANONICAL = "http://validationtest.org/fhir/StructureDefinition/QuantityWithUnlimitedRootCardinality"; + public const string PROFILED_ORG_URL = "http://validationtest.org/fhir/StructureDefinition/ProfiledOrganization"; private static StructureDefinition buildQuantityWithUnlimitedRootCardinality() { @@ -632,5 +642,19 @@ private static StructureDefinition buildPatientWithDeceasedConstraints(string pr }.OfType(FHIRAllTypes.Extension, "http://validationtest.org/fhir/StructureDefinition/MyRangeExtension")); return result; } + + private static IEnumerable buildPatientWithProfiledReferences() + { + yield return createTestSD(PROFILED_ORG_URL, "A profiled organization", + "A profiled Organization with no additional constraints", FHIRAllTypes.Organization); + + var result = createTestSD("http://validationtest.org/fhir/StructureDefinition/PatientWithReferences", "Patient with References", + "Test Patient which has a profiled managing organization", FHIRAllTypes.Patient); + var cons = result.Differential.Element; + + cons.Add(new ElementDefinition("Patient").OfType(FHIRAllTypes.Patient)); + cons.Add(new ElementDefinition("Patient.managingOrganization").OfReference(PROFILED_ORG_URL)); + yield return result; + } } } diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/VisitResolver.cs b/src/Hl7.Fhir.Specification.Tests/Validation/VisitResolver.cs new file mode 100644 index 0000000000..17cb342137 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/Validation/VisitResolver.cs @@ -0,0 +1,32 @@ +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Source; +using System.Collections.Generic; + +namespace Hl7.Fhir.Specification.Tests +{ + public class VisitResolver : IResourceResolver + { + private readonly Resource _example; + public List Visits = new(); + + public VisitResolver(Resource example = null) + { + _example = example; + } + + public Resource ResolveByCanonicalUri(string uri) + { + Visits.Add(uri); + return _example; + } + + public Resource ResolveByUri(string uri) + { + Visits.Add(uri); + return _example; + } + + internal bool Visited(string uri) => Visits.Contains(uri); + } +} + diff --git a/src/Hl7.Fhir.Specification/Validation/ChildConstraintValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/ChildConstraintValidationExtensions.cs index b1871c716c..7fdb0b308e 100644 --- a/src/Hl7.Fhir.Specification/Validation/ChildConstraintValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/ChildConstraintValidationExtensions.cs @@ -24,7 +24,7 @@ namespace Hl7.Fhir.Validation internal static class ChildConstraintValidationExtensions { internal static OperationOutcome ValidateChildConstraints(this Validator validator, ElementDefinitionNavigator definition, - ScopedNode instance, bool allowAdditionalChildren) + ScopedNode instance, bool allowAdditionalChildren, ValidationState state) { var outcome = new OperationOutcome(); if (!definition.HasChildren) return outcome; @@ -32,7 +32,7 @@ internal static OperationOutcome ValidateChildConstraints(this Validator validat validator.Trace(outcome, "Start validation of inlined child constraints for '{0}'".FormatWith(definition.Path), Issue.PROCESSING_PROGRESS, instance); // validate the type on the parent of children. If this is a reference type, it will follow that reference as well - outcome.Add(validator.ValidateTypeReferences(definition.Current.Type, instance, validateProfiles: false)); + outcome.Add(validator.ValidateTypeReferences(definition.Current.Type, instance, state, validateProfiles: false)); var matchResult = ChildNameMatcher.Match(definition, instance); @@ -49,13 +49,13 @@ internal static OperationOutcome ValidateChildConstraints(this Validator validat // Recursively validate my children foreach (var match in matchResult.Matches) { - outcome.Add(validator.validateMatch(match, instance)); + outcome.Add(validator.validateMatch(match, instance, state)); } return outcome; } - private static OperationOutcome validateMatch(this Validator validator, Match match, ScopedNode parent) + private static OperationOutcome validateMatch(this Validator validator, Match match, ScopedNode parent, ValidationState state) { var outcome = new OperationOutcome(); @@ -83,7 +83,7 @@ static bool isExtension(ElementDefinition def) try { - bucket = BucketFactory.CreateRoot(match.Definition, resolver, validator); + bucket = BucketFactory.CreateRoot(match.Definition, resolver, validator, state); } catch (NotImplementedException ni) { diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/BaseBucket.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/BaseBucket.cs index 6c6f527c80..fb9eb8676b 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/BaseBucket.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/BaseBucket.cs @@ -7,18 +7,20 @@ namespace Hl7.Fhir.Validation { internal abstract class BaseBucket : IBucket { - internal protected BaseBucket(ElementDefinition definition) + protected internal BaseBucket(ElementDefinition definition, ValidationState state) { Name = definition.Path + (definition.SliceName != null ? $":{definition.SliceName}" : null); Cardinality = Cardinality.FromElementDefinition(definition); + State = state; } public string Name { get; private set; } public Cardinality Cardinality { get; private set; } public IList Members { get; private set; } = new List(); + protected ValidationState State { get; } public abstract bool Add(ITypedElement instance); - + public virtual OperationOutcome Validate(Validator validator, ITypedElement errorLocation) { var outcome = new OperationOutcome(); diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/BindingDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/BindingDiscriminator.cs index 52fb706f3a..ec1a8e0dfc 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/BindingDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/BindingDiscriminator.cs @@ -8,9 +8,6 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; -using Hl7.Fhir.Utility; -using Hl7.FhirPath; -using System.Linq; namespace Hl7.Fhir.Validation { @@ -29,7 +26,7 @@ public BindingDiscriminator(ElementDefinition.ElementDefinitionBindingComponent public readonly ElementDefinition.ElementDefinitionBindingComponent Binding; public readonly string Context; - protected override bool MatchInternal(ITypedElement instance) + protected override bool MatchInternal(ITypedElement instance, ValidationState _) { var result = Validator.ValidateBinding(Binding, instance, ErrorLocation, Context); return result.Success; diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/BucketFactory.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/BucketFactory.cs index bb9894b6fc..29d1b0383f 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/BucketFactory.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/BucketFactory.cs @@ -15,18 +15,18 @@ namespace Hl7.Fhir.Validation { internal static class BucketFactory { - public static IBucket CreateRoot(ElementDefinitionNavigator root, IResourceResolver resolver, Validator validator) + public static IBucket CreateRoot(ElementDefinitionNavigator root, IResourceResolver resolver, Validator validator, ValidationState state) { // Create a single bucket - var entryBucket = new ElementBucket(root, validator); + var entryBucket = new ElementBucket(root, validator, state); if (root.Current.Slicing == null) return entryBucket; else - return CreateGroup(root, resolver, validator, entryBucket); + return CreateGroup(root, resolver, validator, entryBucket, state); } - public static IBucket CreateGroup(ElementDefinitionNavigator root, IResourceResolver resolver, Validator validator, IBucket entryBucket) + public static IBucket CreateGroup(ElementDefinitionNavigator root, IResourceResolver resolver, Validator validator, IBucket entryBucket, ValidationState state) { var discriminatorSpecs = root.Current.Slicing.Discriminator.ToArray(); // copy, since root will move after this var location = root.Current.Path; @@ -48,16 +48,16 @@ public static IBucket CreateGroup(ElementDefinitionNavigator root, IResourceReso { throw new IncorrectElementDefinitionException($"There is nothing to discriminate on at {location}."); } - subBucket = new DiscriminatorBucket(root, validator, discriminators.ToArray()); + subBucket = new DiscriminatorBucket(root, validator, discriminators.ToArray(), state); } else // Discriminator-less matching - subBucket = new ConstraintsBucket(root, validator); + subBucket = new ConstraintsBucket(root, validator, state); if (root.Current.Slicing == null) subs.Add(subBucket); else - subs.Add(CreateGroup(root, resolver, validator, subBucket)); + subs.Add(CreateGroup(root, resolver, validator, subBucket, state)); } root.ReturnToBookmark(bm); diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/CombinedDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/CombinedDiscriminator.cs index cd132299c2..a9e11cd025 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/CombinedDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/CombinedDiscriminator.cs @@ -22,6 +22,6 @@ internal class CombinedDiscriminator : IDiscriminator public CombinedDiscriminator(IEnumerable components) => Components = components.ToArray(); - public bool Matches(ITypedElement candidate) => Components.All(c => c.Matches(candidate)); + public bool Matches(ITypedElement candidate, ValidationState state) => Components.All(c => c.Matches(candidate, state)); } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ComprehensiveDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ComprehensiveDiscriminator.cs index 32c73dee23..924e88e20c 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ComprehensiveDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ComprehensiveDiscriminator.cs @@ -18,7 +18,7 @@ namespace Hl7.Fhir.Validation /// internal class ComprehensiveDiscriminator : IDiscriminator { - public bool Matches(ITypedElement candidate) => true; + public bool Matches(ITypedElement candidate, ValidationState _) => true; } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ConstraintsBucket.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ConstraintsBucket.cs index a9f17a7a7f..8726f5701d 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ConstraintsBucket.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ConstraintsBucket.cs @@ -6,10 +6,9 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Support; -using Hl7.Fhir.ElementModel; namespace Hl7.Fhir.Validation { @@ -21,21 +20,25 @@ internal class ConstraintsBucket : BaseBucket /// /// The set of constraints that are the criterium for membership of the slice. /// A validator instance that will be invoked to validate the child constraints. - public ConstraintsBucket(ElementDefinitionNavigator sliceConstraints, Validator validator) : base(sliceConstraints.Current) + /// The validation state to forward to all nested validations in this bucket. + public ConstraintsBucket( + ElementDefinitionNavigator sliceConstraints, + Validator validator, + ValidationState state) : base(sliceConstraints.Current, state) { // Keep a copy of the constraints for this slice, so we can use them to validate the instances against later. SliceConstraints = sliceConstraints.ShallowCopy(); Validator = validator; } - + public ElementDefinitionNavigator SliceConstraints { get; private set; } public Validator Validator { get; private set; } public override bool Add(ITypedElement candidate) { - OperationOutcome outcome = Validator.Validate(candidate, SliceConstraints); + OperationOutcome outcome = Validator.ValidateInternal(candidate, SliceConstraints, State); if (outcome.Success) { diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/DiscriminatorBucket.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/DiscriminatorBucket.cs index 54efc2efef..3455ccbbc6 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/DiscriminatorBucket.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/DiscriminatorBucket.cs @@ -24,7 +24,12 @@ internal class DiscriminatorBucket : BaseBucket /// The set of constraints that will be validated for members of the bucket. /// A validator instance that will be invoked to validate the child constraints. /// A set of discriminators that determine whether or not an instance is part of this bucket. - public DiscriminatorBucket(ElementDefinitionNavigator sliceConstraints, Validator validator, IDiscriminator[] discriminators) : base(sliceConstraints.Current) + /// The validation state to forward to all nested validations in this bucket. + public DiscriminatorBucket( + ElementDefinitionNavigator sliceConstraints, + Validator validator, + IDiscriminator[] discriminators, + ValidationState state) : base(sliceConstraints.Current, state) { if (discriminators == null || discriminators.Length == 0) throw Error.InvalidOperation($"Discriminator bucket requires at least one discriminator. Otherwise, use the ConstraintsBucket instead."); @@ -44,7 +49,7 @@ public DiscriminatorBucket(ElementDefinitionNavigator sliceConstraints, Validato public override bool Add(ITypedElement candidate) { - if (Discriminators.All(d => d.Matches(candidate))) + if (Discriminators.All(d => d.Matches(candidate, State))) { Members.Add(candidate); return true; @@ -59,7 +64,7 @@ public override OperationOutcome Validate(Validator validator, ITypedElement err // Simply validate all members and report errors foreach (var member in Members) - outcome.Add(Validator.Validate(member, SliceConstraints)); + outcome.Add(Validator.ValidateInternal(member, SliceConstraints, State)); // include errors reported by our base as well outcome.Add(base.Validate(validator, errorLocation)); diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ElementBucket.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ElementBucket.cs index 5c34917104..8e59025ff9 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ElementBucket.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ElementBucket.cs @@ -6,16 +6,16 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Support; -using Hl7.Fhir.ElementModel; namespace Hl7.Fhir.Validation { internal class ElementBucket : BaseBucket { - public ElementBucket(ElementDefinitionNavigator root, Validator validator) : base(root.Current) + public ElementBucket(ElementDefinitionNavigator root, Validator validator, ValidationState state) : base(root.Current, state) { Root = root.ShallowCopy(); Validator = validator; @@ -36,9 +36,9 @@ public override OperationOutcome Validate(Validator validator, ITypedElement err { var outcome = base.Validate(validator, errorLocation); - foreach(var member in Members) + foreach (var member in Members) { - outcome.Include(Validator.Validate(member, Root)); + outcome.Include(Validator.ValidateInternal(member, Root, State)); } return outcome; diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ExistsDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ExistsDiscriminator.cs index 716145df33..de81117de2 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ExistsDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ExistsDiscriminator.cs @@ -24,7 +24,7 @@ public ExistsDiscriminator(bool existsMode, string path) public string Path { get; } - public bool Matches(ITypedElement candidate) + public bool Matches(ITypedElement candidate, ValidationState _) { ITypedElement[] values = candidate.Select(Path).ToArray(); diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/IDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/IDiscriminator.cs index b49aaf10cd..7fd6c01b25 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/IDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/IDiscriminator.cs @@ -12,6 +12,6 @@ namespace Hl7.Fhir.Validation { internal interface IDiscriminator { - bool Matches(ITypedElement candidate); + bool Matches(ITypedElement candidate, ValidationState state); } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/PathBasedDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/PathBasedDiscriminator.cs index 473038b677..d2c92baa6d 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/PathBasedDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/PathBasedDiscriminator.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Utility; using Hl7.FhirPath; -using System.Collections.Generic; using System.Linq; namespace Hl7.Fhir.Validation @@ -23,7 +22,7 @@ public PathBasedDiscriminator(string path) public readonly string Path; - public bool Matches(ITypedElement candidate) + public bool Matches(ITypedElement candidate, ValidationState state) { ITypedElement[] values = candidate.Select(Path).ToArray(); @@ -34,9 +33,9 @@ public bool Matches(ITypedElement candidate) else if (values.Length == 0) return false; else - return MatchInternal(values.Single()); + return MatchInternal(values.Single(), state); } - abstract protected bool MatchInternal(ITypedElement candidate); + protected abstract bool MatchInternal(ITypedElement candidate, ValidationState state); } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/PatternDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/PatternDiscriminator.cs index fe8cfef283..b70e8fa12c 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/PatternDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/PatternDiscriminator.cs @@ -22,7 +22,7 @@ public PatternDiscriminator(Element pattern, string path, Validator validator) : public readonly Validator Validator; public readonly Element Pattern; - protected override bool MatchInternal(ITypedElement instance) + protected override bool MatchInternal(ITypedElement instance, ValidationState _) { var result = Validator.ValidatePattern(Pattern, instance); return result.Success; diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ProfileDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ProfileDiscriminator.cs index 56edfaa5eb..21ac90f34e 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ProfileDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ProfileDiscriminator.cs @@ -23,13 +23,17 @@ public ProfileDiscriminator(IEnumerable profiles, string path, Validator public readonly Validator Validator; public readonly string[] Profiles; - protected override bool MatchInternal(ITypedElement instance) => - Profiles.Any(profile => validates(instance, profile)); + protected override bool MatchInternal(ITypedElement instance, ValidationState state) => + Profiles.Any(profile => validates(instance, profile, state)); - private bool validates(ITypedElement instance, string profile) + private bool validates(ITypedElement instance, string profile, ValidationState state) { var validator = Validator.NewInstance(); - var result = validator.Validate(instance, profile); + var result = validator.ValidateInternal(instance, + declaredTypeProfile: null, + statedCanonicals: new[] { profile }, + statedProfiles: null, + state: state); return result.Success; } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/TypeDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/TypeDiscriminator.cs index bc2d214a1e..43d0a189b8 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/TypeDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/TypeDiscriminator.cs @@ -25,7 +25,7 @@ public TypeDiscriminator(string[] types, string path, Validator validator) : bas // This discriminator matches if the type in the instance is *compatible* with ANY of the // possible multiple types in the ElementDefinition.Type.Code (since Type repeats). - protected override bool MatchInternal(ITypedElement instance) => + protected override bool MatchInternal(ITypedElement instance, ValidationState _) => Types.Any(type => ModelInfo.IsInstanceTypeFor(type, instance.InstanceType)); } } diff --git a/src/Hl7.Fhir.Specification/Validation/Slicing/ValueDiscriminator.cs b/src/Hl7.Fhir.Specification/Validation/Slicing/ValueDiscriminator.cs index 608dd8a3c3..124129d882 100644 --- a/src/Hl7.Fhir.Specification/Validation/Slicing/ValueDiscriminator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Slicing/ValueDiscriminator.cs @@ -22,7 +22,7 @@ public ValueDiscriminator(Element fixedValue, string path, Validator validator) public readonly Validator Validator; public readonly Element FixedValue; - protected override bool MatchInternal(ITypedElement instance) + protected override bool MatchInternal(ITypedElement instance, ValidationState _) { var result = Validator.ValidateFixed(FixedValue, instance); return result.Success; diff --git a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs index 26ac113359..0cf53de6e5 100644 --- a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs @@ -19,7 +19,7 @@ namespace Hl7.Fhir.Validation { internal static class TypeRefValidationExtensions { - internal static OperationOutcome ValidateType(this Validator validator, ElementDefinition definition, ScopedNode instance) + internal static OperationOutcome ValidateType(this Validator validator, ElementDefinition definition, ScopedNode instance, ValidationState state) { var outcome = new OperationOutcome(); @@ -51,7 +51,7 @@ internal static OperationOutcome ValidateType(this Validator validator, ElementD // Instance typename must be one of the applicable types in the choice if (applicableChoices.Any()) { - outcome.Include(validator.ValidateTypeReferences(applicableChoices, instance)); + outcome.Include(validator.ValidateTypeReferences(applicableChoices, instance, state)); } else { @@ -71,7 +71,7 @@ internal static OperationOutcome ValidateType(this Validator validator, ElementD else if (choices.Count() == 1) { // Only one type present in list of typerefs, all of the typerefs are candidates - outcome.Include(validator.ValidateTypeReferences(typeRefs, instance)); + outcome.Include(validator.ValidateTypeReferences(typeRefs, instance, state)); } return outcome; @@ -79,7 +79,7 @@ internal static OperationOutcome ValidateType(this Validator validator, ElementD internal static OperationOutcome ValidateTypeReferences(this Validator validator, - IEnumerable typeRefs, ScopedNode instance, bool validateProfiles = true) + IEnumerable typeRefs, ScopedNode instance, ValidationState state, bool validateProfiles = true) { //TODO: It's more efficient to do the non-reference types FIRST, since ANY match would be ok, //and validating non-references is cheaper @@ -87,12 +87,16 @@ internal static OperationOutcome ValidateTypeReferences(this Validator validator //better separate the fetching of the instance from the validation, so we do not run the rest of the validation (multiple times!) //when a reference cannot be resolved. (this happens in a choice type where there are multiple references with multiple profiles) - IEnumerable> validations = typeRefs.Select(tr => createValidatorForTypeRef(validator, instance, tr, validateProfiles)); + IEnumerable> validations = typeRefs.Select(tr => createValidatorForTypeRef(validator, instance, tr, validateProfiles, state)); return validator.Combine(BatchValidationMode.Any, instance, validations); } - private static Func createValidatorForTypeRef(Validator validator, ScopedNode instance, ElementDefinition.TypeRefComponent tr, - bool validateProfiles) + private static Func createValidatorForTypeRef( + Validator validator, + ScopedNode instance, + ElementDefinition.TypeRefComponent tr, + bool validateProfiles, + ValidationState state) { return validate; @@ -103,18 +107,22 @@ OperationOutcome validate() if (validateProfiles) { // First, call Validate() for the current element (the reference itself) against the profile - result.Add(validator.Validate(instance, tr.GetDeclaredProfiles(), statedCanonicals: null, statedProfiles: null)); + result.Add(validator.ValidateInternal(instance, tr.GetDeclaredProfiles(), statedCanonicals: null, statedProfiles: null, state: state)); } // If this is a reference, also validate the reference against the targetProfile if (ModelInfo.FhirTypeNameToFhirType(tr.Code) == FHIRAllTypes.Reference) - result.Add(validator.ValidateResourceReference(instance, tr)); + result.Add(validator.ValidateResourceReference(instance, tr, state)); return result; } } - internal static OperationOutcome ValidateResourceReference(this Validator validator, ScopedNode instance, ElementDefinition.TypeRefComponent typeRef) + internal static OperationOutcome ValidateResourceReference( + this Validator validator, + ScopedNode instance, + ElementDefinition.TypeRefComponent typeRef, + ValidationState state) { var outcome = new OperationOutcome(); @@ -123,7 +131,6 @@ internal static OperationOutcome ValidateResourceReference(this Validator valida if (reference == null) // No reference found -> this is always valid return outcome; - // Try to resolve the reference *within* the current instance (Bundle, resource with contained resources) first var referencedResource = validator.resolveReference(instance, reference, out ElementDefinition.AggregationMode? encounteredKind, outcome); @@ -165,12 +172,24 @@ internal static OperationOutcome ValidateResourceReference(this Validator valida if (encounteredKind != ElementDefinition.AggregationMode.Referenced) { - childResult = validator.Validate(referencedResource, typeRef.TargetProfile, statedProfiles: null, statedCanonicals: null); + childResult = validator.ValidateInternal(referencedResource, + typeRef.TargetProfile, + statedProfiles: null, + statedCanonicals: null, + state: state); } else { var newValidator = validator.NewInstance(); - childResult = newValidator.Validate(referencedResource, typeRef.TargetProfile, statedProfiles: null, statedCanonicals: null); + var newState = state.NewInstanceScope(); + + newState.Instance.ExternalUrl = reference; + childResult = newState.Global.ExternalValidations.Start(reference, typeRef.TargetProfile, + () => newValidator.ValidateInternal(referencedResource, + typeRef.TargetProfile, + statedProfiles: null, + statedCanonicals: null, + state: newState)); } // Prefix each path with the referring resource's path to keep the locations diff --git a/src/Hl7.Fhir.Specification/Validation/ValidationLogger.cs b/src/Hl7.Fhir.Specification/Validation/ValidationLogger.cs new file mode 100644 index 0000000000..12f73eb7ee --- /dev/null +++ b/src/Hl7.Fhir.Specification/Validation/ValidationLogger.cs @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +using Hl7.Fhir.Model; +using Hl7.Fhir.Support; +using System; +using System.Collections.Generic; + +#nullable enable + +namespace Hl7.Fhir.Validation +{ + /// + /// This class is used to track which resources/contained resources/bundled resources are already + /// validated, and what the previous result was. Since it keeps track of running validations, it + /// can also be used to detect loops. + /// + internal class ValidationLogger + { + private enum ValidationStatus + { + Started, + Success, + Failure + } + + private class Entry + { + public Entry(string location, string profileUrl, ValidationStatus status) + { + Location = location; + ProfileUrl = profileUrl; + Status = status; + } + + public string Location; + public string ProfileUrl; + public ValidationStatus Status; + } + + private readonly Dictionary _data = new(); + + public OperationOutcome Start(string location, string profileUrl, Func validator) + { + var key = $"{location}:{profileUrl}"; + + if (_data.TryGetValue(key, out var existing)) + { + if (existing.Status == ValidationStatus.Started) + throw new InvalidOperationException($"Detected a loop: instance data inside '{location}' refers back to itself."); + + // If the validation has been run before, return an outcome with the same result. + // Note: we don't keep a copy of the original outcome since it has been included in the + // total result (at the first run) and keeping them around costs a lot of memory. + var repeatedOutcome = new OperationOutcome(); + if (existing.Status == ValidationStatus.Failure) + repeatedOutcome.AddIssue("Validation of this resource has been performed before with a failure result.", + Issue.PROCESSING_REPEATED_ERROR); + + return repeatedOutcome; + } + else + { + // This validation is run for the first time + var newEntry = new Entry(location, profileUrl, ValidationStatus.Started); + _data.Add(key, newEntry); + + var result = validator(); + newEntry.Status = result.Success ? ValidationStatus.Success : ValidationStatus.Failure; + return result; + } + } + + public int Count => _data.Values.Count; + } +} diff --git a/src/Hl7.Fhir.Specification/Validation/ValidationState.cs b/src/Hl7.Fhir.Specification/Validation/ValidationState.cs new file mode 100644 index 0000000000..30521aa8f2 --- /dev/null +++ b/src/Hl7.Fhir.Specification/Validation/ValidationState.cs @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + + +#nullable enable + +namespace Hl7.Fhir.Validation +{ + internal class Counter + { + public int Increase() => Value += 1; + + public int Value { get; private set; } + } + + + /// + /// Hold the state relevant to a single run of the validator. + /// + internal class ValidationState + { + /// + /// State to be kept for one full run of the validator. + /// + public class GlobalState + { + /// + /// This keeps track of all validations done on external resources + /// referenced from the original root resource passed to the Validate() call. + /// + public ValidationLogger ExternalValidations { get; set; } = new(); + + public Counter ResourcesValidated { get; set; } = new(); + } + + public GlobalState Global { get; private set; } = new(); + + /// + /// State to be kept for an instance encountered during validation + /// (e.g. if the resource under validation references an external resource, + /// the validation of that resource will have its own . + /// + public class InstanceState + { + /// + /// The URL where the current instance was retrieved (if known). + /// + public string? ExternalUrl { get; set; } + + public ValidationLogger InternalValidations { get; set; } = new(); + } + + public InstanceState Instance { get; private set; } = new(); + + public ValidationState NewInstanceScope() => + new() + { + Global = Global + }; + } +} diff --git a/src/Hl7.Fhir.Specification/Validation/Validator.cs b/src/Hl7.Fhir.Specification/Validation/Validator.cs index f7deb6a330..b04bc9ebe9 100644 --- a/src/Hl7.Fhir.Specification/Validation/Validator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Validator.cs @@ -40,7 +40,7 @@ public class Validator private FhirPathCompiler _fpCompiler; #if REUSE_SNAPSHOT_GENERATOR - SnapshotGenerator _snapshotGenerator; + private SnapshotGenerator _snapshotGenerator; internal SnapshotGenerator SnapshotGenerator { @@ -83,41 +83,57 @@ internal Validator NewInstance() }; } - public OperationOutcome Validate(ITypedElement instance) { - return Validate(instance, declaredTypeProfile: null, statedCanonicals: null, statedProfiles: null).RemoveDuplicateMessages(); + var state = new ValidationState(); + var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: null, statedProfiles: null, state: state) + .RemoveDuplicateMessages(); + result.SetAnnotation(state); + return result; } - public OperationOutcome Validate(ITypedElement instance, params string[] definitionUris) - { - return Validate(instance, (IEnumerable)definitionUris).RemoveDuplicateMessages(); ; - } + public OperationOutcome Validate(ITypedElement instance, params string[] definitionUris) => + Validate(instance, (IEnumerable)definitionUris); public OperationOutcome Validate(ITypedElement instance, IEnumerable definitionUris) { - return Validate(instance, declaredTypeProfile: null, statedCanonicals: definitionUris, statedProfiles: null).RemoveDuplicateMessages(); ; + var state = new ValidationState(); + var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: definitionUris, statedProfiles: null, state: state) + .RemoveDuplicateMessages(); + result.SetAnnotation(state); + return result; } - public OperationOutcome Validate(ITypedElement instance, params StructureDefinition[] structureDefinitions) - { - return Validate(instance, (IEnumerable)structureDefinitions).RemoveDuplicateMessages(); - } + public OperationOutcome Validate(ITypedElement instance, params StructureDefinition[] structureDefinitions) => + Validate(instance, (IEnumerable)structureDefinitions); public OperationOutcome Validate(ITypedElement instance, IEnumerable structureDefinitions) { - return Validate(instance, declaredTypeProfile: null, statedCanonicals: null, statedProfiles: structureDefinitions).RemoveDuplicateMessages(); ; + var state = new ValidationState(); + var result = ValidateInternal( + instance, + declaredTypeProfile: null, + statedCanonicals: null, + statedProfiles: structureDefinitions, + state: state).RemoveDuplicateMessages(); + result.SetAnnotation(state); + return result; } // This is the one and only main entry point for all external validation calls (i.e. invoked by the user of the API) - internal OperationOutcome Validate(ITypedElement instance, string declaredTypeProfile, IEnumerable statedCanonicals, IEnumerable statedProfiles) + internal OperationOutcome ValidateInternal( + ITypedElement instance, + string declaredTypeProfile, + IEnumerable statedCanonicals, + IEnumerable statedProfiles, + ValidationState state) { var processor = new ProfilePreprocessor(profileResolutionNeeded, snapshotGenerationNeeded, instance, declaredTypeProfile, statedProfiles, statedCanonicals, Settings.ResourceMapping); var outcome = processor.Process(); // Note: only start validating if the profiles are complete and consistent if (outcome.Success) - outcome.Add(Validate(instance, processor.Result)); + outcome.Add(ValidateInternal(instance, processor.Result, state)); return outcome; @@ -128,26 +144,23 @@ StructureDefinition profileResolutionNeeded(string canonical) => #pragma warning restore CS0618 // Type or member is obsolete } - internal OperationOutcome Validate(ITypedElement instance, ElementDefinitionNavigator definition) - { - return Validate(instance, new[] { definition }).RemoveDuplicateMessages(); ; - } + internal OperationOutcome ValidateInternal(ITypedElement instance, ElementDefinitionNavigator definition, ValidationState state) + => ValidateInternal(instance, new[] { definition }, state).RemoveDuplicateMessages(); // This is the one and only main internal entry point for all validations, which in its term // will call step 1 in the validator, the function validateElement - internal OperationOutcome Validate(ITypedElement elementNav, IEnumerable definitions) + internal OperationOutcome ValidateInternal(ITypedElement elementNav, IEnumerable definitions, ValidationState state) { var outcome = new OperationOutcome(); - - var instance = elementNav as ScopedNode ?? new ScopedNode(elementNav); + var instance = elementNav.ToScopedNode(); try { var allDefinitions = definitions.ToList(); if (allDefinitions.Count() == 1) - outcome.Add(validateElement(allDefinitions.Single(), instance)); + outcome.Add(startValidation(allDefinitions.Single(), instance, state)); else { var validators = allDefinitions.Select(nav => createValidator(nav)); @@ -162,17 +175,36 @@ internal OperationOutcome Validate(ITypedElement elementNav, IEnumerable createValidator(ElementDefinitionNavigator nav) => - () => validateElement(nav, instance); + () => startValidation(nav, instance, state); + + } + private OperationOutcome startValidation(ElementDefinitionNavigator definition, ScopedNode instance, ValidationState state) + { + // If we are starting a validation of a referenceable element (resource, contained resource, nested resource), + // make sure we keep track of it, so we can detect loops and avoid validating the same resource multiple times. + if (instance.AtResource && definition.AtRoot) + { + state.Global.ResourcesValidated.Increase(); + var location = state.Instance.ExternalUrl is string extu + ? extu + "#" + instance.Location + : instance.Location; + + return state.Instance.InternalValidations.Start(location, definition.StructureDefinition.Url, + () => validateElement(definition, instance, state)); + } + else + { + return validateElement(definition, instance, state); + } } - private OperationOutcome validateElement(ElementDefinitionNavigator definition, ScopedNode instance) + private OperationOutcome validateElement(ElementDefinitionNavigator definition, ScopedNode instance, ValidationState state) { var outcome = new OperationOutcome(); try { - // If navigator cannot be moved to content, there's really nothing to validate against. if (definition.AtRoot && !definition.MoveToFirstChild()) { @@ -217,13 +249,13 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, // TODO: Check whether this is even true when the has a profile? // Note: the snapshot is *not* exhaustive if the declared type is a base FHIR type (like Resource), // in which case there may be additional children (verified in the next step) - outcome.Add(this.ValidateChildConstraints(definition, instance, allowAdditionalChildren: allowAdditionalChildren)); + outcome.Add(this.ValidateChildConstraints(definition, instance, allowAdditionalChildren: allowAdditionalChildren, state)); // Special case: if we are located at a nested resource (i.e. contained or Bundle.entry.resource), // we need to validate based on the actual type of the instance if (isInlineChildren && elementConstraints.IsResourcePlaceholder()) { - outcome.Add(this.ValidateType(elementConstraints, instance)); + outcome.Add(this.ValidateType(elementConstraints, instance, state)); } } @@ -232,8 +264,8 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, // No inline-children, so validation depends on the presence of a or if (elementConstraints.Type != null || elementConstraints.ContentReference != null) { - outcome.Add(this.ValidateType(elementConstraints, instance)); - outcome.Add(ValidateNameReference(elementConstraints, definition, instance)); + outcome.Add(this.ValidateType(elementConstraints, instance, state)); + outcome.Add(ValidateNameReference(elementConstraints, definition, instance, state)); } else Trace(outcome, "ElementDefinition has no child, nor does it specify a type or contentReference to validate the instance data against", Issue.PROFILE_ELEMENTDEF_CONTAINS_NO_TYPE_OR_NAMEREF, instance); @@ -263,6 +295,7 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, } } + private OperationOutcome ValidateExtension(IExtendable elementDef, ITypedElement instance, string uri) { var outcome = new OperationOutcome(); @@ -343,7 +376,11 @@ internal OperationOutcome ValidateBinding(ElementDefinition.ElementDefinitionBin return outcome; } - internal OperationOutcome ValidateNameReference(ElementDefinition definition, ElementDefinitionNavigator allDefinitions, ScopedNode instance) + internal OperationOutcome ValidateNameReference( + ElementDefinition definition, + ElementDefinitionNavigator allDefinitions, + ScopedNode instance, + ValidationState state) { var outcome = new OperationOutcome(); @@ -354,7 +391,7 @@ internal OperationOutcome ValidateNameReference(ElementDefinition definition, El var referencedPositionNav = allDefinitions.ShallowCopy(); if (referencedPositionNav.JumpToNameReference(definition.ContentReference)) - outcome.Include(Validate(instance, referencedPositionNav)); + outcome.Include(ValidateInternal(instance, referencedPositionNav, state)); else Trace(outcome, $"ElementDefinition uses a non-existing nameReference '{definition.ContentReference}'", Issue.PROFILE_ELEMENTDEF_INVALID_NAMEREFERENCE, instance); From ede19736c194c52785e37f302fdb3eef7e342587 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Mon, 28 Mar 2022 15:29:06 +0200 Subject: [PATCH 09/55] Replaced constant "specification.zip" with ConformanceSource(). --- .../ElementNodeTests.cs | 22 +-- .../ScopedNodeTests.cs | 4 +- .../Snapshot/SnapshotGeneratorTest.cs | 179 +++++++++--------- .../StructureDefinitionWalkerTests.cs | 2 +- .../Validation/BasicValidationTests.cs | 6 +- .../Validation/ProfileAssertionTests.cs | 13 +- .../Validation/ProfilePreprocessorTests.cs | 4 +- .../TargetProfileValidationTests.cs | 2 +- 8 files changed, 113 insertions(+), 119 deletions(-) diff --git a/src/Hl7.Fhir.ElementModel.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Tests/ElementNodeTests.cs index 77cf654023..f7c550f28b 100644 --- a/src/Hl7.Fhir.ElementModel.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Tests/ElementNodeTests.cs @@ -9,19 +9,19 @@ // To introduce the DSTU2 FHIR specification //extern alias dstu2; -using System; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Utility; -using System.Linq; +using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; -using System.IO; using Hl7.Fhir.Specification; -using Hl7.Fhir.Model; +using Hl7.Fhir.Specification.Snapshot; +using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Tests; +using Hl7.Fhir.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Hl7.Fhir.Specification.Source; +using System; +using System.IO; +using System.Linq; using System.Threading.Tasks; -using Hl7.Fhir.Specification.Snapshot; using Tasks = System.Threading.Tasks; namespace Hl7.FhirPath.Tests @@ -41,7 +41,7 @@ private ElementNode createPatient() var activeNode = patientRoot.Add(_provider, "active", true); activeNode.Add(_provider, "id", "myId1"); - + activeNode.AddAnnotation("a string annotation"); var ext1 = activeNode.Add(_provider, "extension"); ext1.Add(_provider, "value", 4, "integer"); @@ -320,10 +320,10 @@ public void FromElementClonesCorrectly() Assert.IsTrue(activeChild.Annotations().Single() == "a string annotation"); var identifierSystemChild = newElement["identifier"][0]["system"].Single(); - + // check whether we really have a clone by changing the copy identifierSystemChild.Value = "http://dan.nl"; - Assert.AreEqual("http://nu.nl",patient["identifier"][0]["system"].Single().Value); + Assert.AreEqual("http://nu.nl", patient["identifier"][0]["system"].Single().Value); } [TestMethod] @@ -386,7 +386,7 @@ private class CustomResourceResolver : IAsyncResourceResolver public CustomResourceResolver() { - _zipSource = new ZipSource("specification.zip"); + _zipSource = ZipSource.CreateValidationSource(); _resolver = new CachedResolver(new MultiResolver(_zipSource, new DirectorySource("TestData/TestSd"))); } diff --git a/src/Hl7.Fhir.ElementModel.Tests/ScopedNodeTests.cs b/src/Hl7.Fhir.ElementModel.Tests/ScopedNodeTests.cs index 19c936aa07..66e9fff332 100644 --- a/src/Hl7.Fhir.ElementModel.Tests/ScopedNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Tests/ScopedNodeTests.cs @@ -17,7 +17,7 @@ namespace Hl7.Fhir.ElementModel.Tests [TestClass] public class ScopedNodeTests { - ScopedNode? _bundleNode; + private ScopedNode? _bundleNode; [TestInitialize] public void SetupSource() @@ -249,7 +249,7 @@ private class CCDAResourceResolver : IAsyncResourceResolver public CCDAResourceResolver() { _cache = new Dictionary(); - _zipSource = new ZipSource("specification.zip"); + _zipSource = ZipSource.CreateValidationSource(); _coreResolver = new CachedResolver(new MultiResolver(_zipSource, new DirectorySource("TestData/TestSd"))); } diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index a4c6262c60..9671f3c2d1 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -20,7 +20,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; @@ -42,12 +41,11 @@ public class PortableSnapshotGeneratorTest public class SnapshotGeneratorTest2 #endif { - SnapshotGenerator _generator; - ZipSource _zipSource; - CachedResolver _testResolver; - TimingSource _source; - - readonly SnapshotGeneratorSettings _settings = new SnapshotGeneratorSettings() + private SnapshotGenerator _generator; + private ZipSource _zipSource; + private CachedResolver _testResolver; + private TimingSource _source; + private readonly SnapshotGeneratorSettings _settings = new SnapshotGeneratorSettings() { // Throw on unresolved profile references; must include in TestData folder GenerateSnapshotForExternalProfiles = true, @@ -440,7 +438,7 @@ private async T.Task fullyExpandElement(ElementDefinitionNavigator nav, List !ModelInfo.IsPrimitive(typeName); + private static bool isComplexDataTypeOrResource(string typeName) => !ModelInfo.IsPrimitive(typeName); - static bool isComplexDataTypeOrResource(FHIRAllTypes type) => !ModelInfo.IsPrimitive(type); + private static bool isComplexDataTypeOrResource(FHIRAllTypes type) => !ModelInfo.IsPrimitive(type); // [WMR 20180115] NEW - Use alternative (iterative) approach for full expansion @@ -576,7 +574,7 @@ public async T.Task TestFullyExpandNLCoreOrganization() } } - static void beforeExpandElementHandler_DEBUG(object sender, SnapshotExpandElementEventArgs e) + private static void beforeExpandElementHandler_DEBUG(object sender, SnapshotExpandElementEventArgs e) { Debug.Print($"[beforeExpandElementHandler_DEBUG] #{e.Element.GetHashCode()} '{e.Element.Path}' - HasChildren = {e.HasChildren} - MustExpand = {e.MustExpand}"); } @@ -632,7 +630,7 @@ public async T.Task GenerateDerivedProfileSnapshot() dumpBasePaths(expanded); } - void assertContainsElement(StructureDefinition sd, string path, string name = null, string elementId = null) + private void assertContainsElement(StructureDefinition sd, string path, string name = null, string elementId = null) { Assert.IsNotNull(sd); @@ -645,7 +643,7 @@ void assertContainsElement(StructureDefinition sd, string path, string name = nu assertContainsElement(sd.Snapshot, path, name, elementId); } - void assertContainsElement(IElementList elements, string path, string name = null, string elementId = null) + private void assertContainsElement(IElementList elements, string path, string name = null, string elementId = null) { var label = elements is StructureDefinition.DifferentialComponent ? "differential" : "snapshot"; Assert.IsNotNull(elements); @@ -671,10 +669,10 @@ private async T.Task generateSnapshot(string url, Action insertElementsBefore(structure.Differential.Element, insertBefore, inserts); - static void insertElementsBefore(List elements, ElementDefinition insertBefore, params ElementDefinition[] inserts) + private static void insertElementsBefore(List elements, ElementDefinition insertBefore, params ElementDefinition[] inserts) { var idx = elements.FindIndex(e => e.Path == insertBefore.Path && e.SliceName == insertBefore.SliceName); Assert.AreNotEqual(-1, idx, $"Warning! insertBefore element is missing. Path = '{insertBefore.Path}', Name = '{insertBefore.SliceName}'."); @@ -686,10 +684,10 @@ static void insertElementsBefore(List elements, ElementDefini elements.InsertRange(idx, inserts); } - static void insertElementsBefore(StructureDefinition structure, string insertBeforePath, int elemIndex, params ElementDefinition[] inserts) + private static void insertElementsBefore(StructureDefinition structure, string insertBeforePath, int elemIndex, params ElementDefinition[] inserts) => insertElementsBefore(structure.Differential.Element, insertBeforePath, elemIndex, inserts); - static void insertElementsBefore(List elements, string insertBeforePath, int elemIndex, params ElementDefinition[] inserts) + private static void insertElementsBefore(List elements, string insertBeforePath, int elemIndex, params ElementDefinition[] inserts) { var idx = -1; do @@ -1021,7 +1019,7 @@ public async T.Task GenerateSnapshotIgnoreMissingExternalProfile() assertIssue(outcome.Issue[2], Issue.UNAVAILABLE_REFERENCED_PROFILE, "http://example.org/fhir/StructureDefinition/MyCodeableConcept"); } - static void assertIssue(OperationOutcome.IssueComponent issue, Issue expected, string diagnostics = null, params string[] location) + private static void assertIssue(OperationOutcome.IssueComponent issue, Issue expected, string diagnostics = null, params string[] location) { Assert.IsNotNull(issue); Assert.AreEqual(expected.Type, issue.Code); @@ -1109,7 +1107,7 @@ private async T.Task generateSnapshot(StructureDefinition o return (areEqual, expanded); } - IEnumerable findConstraintStrucDefs() + private IEnumerable findConstraintStrucDefs() { #if true if (_source.Source is DirectorySource dirSource) @@ -1503,7 +1501,7 @@ private async T.Task verifyExpandElement(ElementDefinition elem, IList enumerateDistinctTypeProfiles(IList elements) + private static IEnumerable enumerateDistinctTypeProfiles(IList elements) => elements.SelectMany(e => e.Type).Select(t => t.Profile).Distinct(); - static string formatElementPathName(ElementDefinition elem) => + private static string formatElementPathName(ElementDefinition elem) => elem == null ? null : !string.IsNullOrEmpty(elem.SliceName) ? @@ -1549,7 +1547,7 @@ static string formatElementPathName(ElementDefinition elem) => : elem.Path; [Conditional("DEBUG")] - static void dumpBaseElems(IEnumerable elements) + private static void dumpBaseElems(IEnumerable elements) { Debug.Print(string.Join(Environment.NewLine, elements.Select(e => @@ -1576,7 +1574,7 @@ static void dumpBaseElems(IEnumerable elements) } [Conditional("DEBUG")] - void dumpBasePaths(StructureDefinition sd) + private void dumpBasePaths(StructureDefinition sd) { if (sd != null && sd.Snapshot != null) { @@ -1594,10 +1592,10 @@ void dumpBasePaths(StructureDefinition sd) } [Conditional("DEBUG")] - void dumpOutcome(OperationOutcome outcome) => dumpIssues(outcome?.Issue); + private void dumpOutcome(OperationOutcome outcome) => dumpIssues(outcome?.Issue); [Conditional("DEBUG")] - void dumpIssues(List issues) + private void dumpIssues(List issues) { if (issues != null && issues.Count > 0) { @@ -1611,7 +1609,7 @@ void dumpIssues(List issues) } [Conditional("DEBUG")] - void dumpIssue(OperationOutcome.IssueComponent issue, int index) + private void dumpIssue(OperationOutcome.IssueComponent issue, int index) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("* Issue #{0}: Severity = '{1}' Code = '{2}'", index, issue.Severity, issue.Code); @@ -2133,7 +2131,7 @@ public async T.Task TestBaseAnnotations_ExtensionDefinition() // [WMR 20170714] NEW // Annotated Base Element for backbone elements is not included in base structuredefinition ? - static StructureDefinition MyTestObservation => new StructureDefinition() + private static StructureDefinition MyTestObservation => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -2202,7 +2200,7 @@ public async T.Task TestBaseAnnotations_BackboneElement() // [WMR 20160816] Test custom annotations containing associated base definitions - class BaseDefAnnotation + private class BaseDefAnnotation { public BaseDefAnnotation(ElementDefinition baseElemDef, StructureDefinition baseStructDef) { @@ -2213,12 +2211,12 @@ public BaseDefAnnotation(ElementDefinition baseElemDef, StructureDefinition base public StructureDefinition BaseStructureDefinition { get; private set; } } - static ElementDefinition GetBaseElementAnnotation(ElementDefinition elemDef) + private static ElementDefinition GetBaseElementAnnotation(ElementDefinition elemDef) { return elemDef?.Annotation()?.BaseElementDefinition; } - void profileHandler(object sender, SnapshotBaseProfileEventArgs e) + private void profileHandler(object sender, SnapshotBaseProfileEventArgs e) { var profile = e.Profile; // Assert.IsTrue(sd.Url != profile.Url || sd.IsExactly(profile)); @@ -2231,7 +2229,7 @@ void profileHandler(object sender, SnapshotBaseProfileEventArgs e) Assert.AreEqual(profile.BaseDefinition, baseProfile.Url); } - static void elementHandler(object sender, SnapshotElementEventArgs e) + private static void elementHandler(object sender, SnapshotElementEventArgs e) { var elem = e.Element; Assert.IsNotNull(elem); @@ -2255,7 +2253,7 @@ static void elementHandler(object sender, SnapshotElementEventArgs e) Debug.WriteLine(ann?.BaseElementDefinition != null ? $" (old Base: #{ann.BaseElementDefinition.GetHashCode()} '{ann.BaseElementDefinition.Path}')" : ""); } - void constraintHandler(object sender, SnapshotConstraintEventArgs e) + private void constraintHandler(object sender, SnapshotConstraintEventArgs e) { if (e.Element is ElementDefinition elem) { @@ -2266,7 +2264,7 @@ void constraintHandler(object sender, SnapshotConstraintEventArgs e) } } - static void assertBaseDefs(StructureDefinition sd, SnapshotGeneratorSettings settings) + private static void assertBaseDefs(StructureDefinition sd, SnapshotGeneratorSettings settings) { Assert.IsNotNull(sd); Assert.IsNotNull(sd.Snapshot); @@ -2274,7 +2272,7 @@ static void assertBaseDefs(StructureDefinition sd, SnapshotGeneratorSettings set assertBaseDefs(sd.Snapshot.Element, settings); } - static void assertBaseDefs(List elems, SnapshotGeneratorSettings settings) + private static void assertBaseDefs(List elems, SnapshotGeneratorSettings settings) { Assert.IsNotNull(elems); Assert.IsTrue(elems.Count > 0); @@ -2336,7 +2334,7 @@ static void assertBaseDefs(List elems, SnapshotGeneratorSetti // Utility function to compare element and base element // Path, Base and CHANGED_BY_DIFF_EXT extension are excluded from comparison // Returns true if the element has no other constraints on base - static bool isAlmostExactly(ElementDefinition elem, ElementDefinition baseElem, bool ignoreTypeProfile = false) + private static bool isAlmostExactly(ElementDefinition elem, ElementDefinition baseElem, bool ignoreTypeProfile = false) { var elemClone = (ElementDefinition)elem.DeepCopy(); var baseClone = (ElementDefinition)baseElem.DeepCopy(); @@ -2367,7 +2365,7 @@ static bool isAlmostExactly(ElementDefinition elem, ElementDefinition baseElem, } // Returns true if the specified element or any of its' components contain the CHANGED_BY_DIFF_EXT extension - static bool hasChanges(ElementDefinition elem) + private static bool hasChanges(ElementDefinition elem) { return isChanged(elem) || hasChanges(elem.AliasElement) @@ -2405,7 +2403,7 @@ static bool hasChanges(ElementDefinition elem) || hasChanges(elem.Type); } - static string getChangeDescription(ElementDefinition element) + private static string getChangeDescription(ElementDefinition element) { if (isChanged(element.Slicing)) { return "Slicing"; } // Moved to front if (hasChanges(element.Type)) { return "Type"; } // Moved to front @@ -2448,8 +2446,8 @@ static string getChangeDescription(ElementDefinition element) return isChanged(element) ? "Element" : string.Empty; // Moved to back } - static bool hasChanges(IList elements) where T : Element => elements != null ? elements.Any(e => isChanged(e)) : false; - static bool isChanged(Element elem) => elem != null && elem.IsConstrainedByDiff(); + private static bool hasChanges(IList elements) where T : Element => elements != null ? elements.Any(e => isChanged(e)) : false; + private static bool isChanged(Element elem) => elem != null && elem.IsConstrainedByDiff(); [TestMethod] public async T.Task TestExpandCoreElement() @@ -2523,7 +2521,7 @@ public async T.Task TestExpandAllCoreResources() await testExpandResources(coreResourceUrls.ToArray()); } - async T.Task testExpandResources(string[] profileUris) + private async T.Task testExpandResources(string[] profileUris) { var sw = new Stopwatch(); int count = profileUris.Length; @@ -2539,7 +2537,7 @@ async T.Task testExpandResources(string[] profileUris) _source.ShowDuration(count, sw.Elapsed); } - async T.Task testExpandResource(string url) + private async T.Task testExpandResource(string url) { Debug.Print("[testExpandResource] url = '{0}'", url); var sd = await _testResolver.FindStructureDefinitionAsync(url); @@ -2563,7 +2561,7 @@ async T.Task testExpandResource(string url) return result; } - IEnumerable enumerateBundleStream(Stream stream) where T : Resource + private IEnumerable enumerateBundleStream(Stream stream) where T : Resource { using (var reader = XmlReader.Create(stream)) { @@ -2611,9 +2609,9 @@ public async T.Task TestExpandCoreTypesByHierarchy() Assert.IsTrue(result); } - struct ProfileInfo { public string Url; public string BaseDefinition; } + private struct ProfileInfo { public string Url; public string BaseDefinition; } - async T.Task expandStructuresBasedOn(IAsyncResourceResolver resolver, ProfileInfo[] profileInfo, string baseUrl) + private async T.Task expandStructuresBasedOn(IAsyncResourceResolver resolver, ProfileInfo[] profileInfo, string baseUrl) { var derivedStructures = profileInfo.Where(pi => pi.BaseDefinition == baseUrl); if (derivedStructures.Any()) @@ -2629,7 +2627,7 @@ async T.Task expandStructuresBasedOn(IAsyncResourceResolver resolver, ProfileInf } } - async T.Task updateSnapshot(StructureDefinition sd) + private async T.Task updateSnapshot(StructureDefinition sd) { Assert.IsNotNull(sd); Debug.Print("Profile: '{0}' : '{1}'".FormatWith(sd.Url, sd.BaseDefinition)); @@ -2642,7 +2640,7 @@ async T.Task updateSnapshot(StructureDefinition sd) } // Verify ElementDefinition.Base components - bool verifyElementBase(StructureDefinition original, StructureDefinition expanded) + private bool verifyElementBase(StructureDefinition original, StructureDefinition expanded) { var originalElems = original.HasSnapshot ? original.Snapshot.Element : new List(); var expandedElems = expanded.HasSnapshot ? expanded.Snapshot.Element : new List(); @@ -2759,7 +2757,7 @@ bool verifyElementBase(StructureDefinition original, StructureDefinition expande return verified; } - static bool verifyBasePath(ElementDefinition elem, ElementDefinition orgElem, string path = "") + private static bool verifyBasePath(ElementDefinition elem, ElementDefinition orgElem, string path = "") { bool result; if (!string.IsNullOrEmpty(path)) @@ -2823,7 +2821,7 @@ public async T.Task TestReslicingOrder() assertPatientTelecomReslice(snapNav); } - void assertPatientTelecomReslice(ElementDefinitionNavigator nav) + private void assertPatientTelecomReslice(ElementDefinitionNavigator nav) { Assert.IsTrue(nav.MoveToFirstChild()); // Patient @@ -2924,7 +2922,7 @@ public async T.Task FindComplexTestExtensions() } // Ewout: type slices cannot contain renamed elements! - static StructureDefinition ObservationTypeSliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeSliceProfile => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -2960,10 +2958,10 @@ public async T.Task FindComplexTestExtensions() }; [Conditional("DEBUG")] - void dumpElements(IEnumerable elements, string header = null) => dumpElements(elements.ToList(), header); + private void dumpElements(IEnumerable elements, string header = null) => dumpElements(elements.ToList(), header); [Conditional("DEBUG")] - void dumpElements(List elements, string header = null) + private void dumpElements(List elements, string header = null) { Debug.WriteLineIf(!string.IsNullOrEmpty(header), header); for (int i = 0; i < elements.Count; i++) @@ -3072,7 +3070,7 @@ public async T.Task TestUnresolvedBaseProfile() assertIssue(outcome.Issue[0], Issue.UNAVAILABLE_REFERENCED_PROFILE, profile.BaseDefinition); } - static StructureDefinition ObservationTypeResliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeResliceProfile => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ObservationTypeSliceProfile.Url, @@ -3153,7 +3151,7 @@ public async T.Task TestTypeReslicing() } // Choice type constraint, with element renaming - static StructureDefinition ObservationTypeConstraintProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeConstraintProfile => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3248,7 +3246,7 @@ public async T.Task TestInvalidChoiceTypeConstraints() assertIssue(outcome.Issue[0], SnapshotGenerator.PROFILE_ELEMENTDEF_INVALID_CHOICE_CONSTRAINT); } - static StructureDefinition ClosedExtensionSliceObservationProfile => new StructureDefinition() + private static StructureDefinition ClosedExtensionSliceObservationProfile => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3323,7 +3321,7 @@ public async T.Task TestSlicingEntryWithChilren() [TestMethod] public async T.Task TestObservationProfileWithExtensions_ExpandAll() => await testObservationProfileWithExtensions(true); - async T.Task testObservationProfileWithExtensions(bool expandAll) + private async T.Task testObservationProfileWithExtensions(bool expandAll) { // Same as TestObservationProfileWithExtensions, but with full expansion of all complex elements (inc. extensions!) @@ -3426,7 +3424,7 @@ async T.Task testObservationProfileWithExtensions(bool expandAll) verifyProfileExtensionBaseElement(coreObsExtensionElem); } - void verifyProfileExtensionBaseElement(ElementDefinition extElem) + private void verifyProfileExtensionBaseElement(ElementDefinition extElem) { var baseElem = extElem.Annotation().BaseElementDefinition; Assert.IsNotNull(baseElem); @@ -3667,7 +3665,7 @@ public async T.Task TestInvalidProfileExtensionTarget() // - Patient.identifier:B/2 => Patient.identifier:B in MyPatient // - Patient.identifier:C => Patient.identifier in MyPatient - static StructureDefinition SlicedPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedPatientProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3785,7 +3783,7 @@ public async T.Task TestSliceBase_SlicedPatient() Assert.AreEqual("2", nav.Current.Max); } - static StructureDefinition NationalPatientProfile => new StructureDefinition() + private static StructureDefinition NationalPatientProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3808,7 +3806,7 @@ public async T.Task TestSliceBase_SlicedPatient() } }; - static StructureDefinition SlicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedNationalPatientProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -4000,7 +3998,7 @@ public async T.Task TestSliceBase_SlicedNationalPatient() #endif } - static StructureDefinition ReslicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition ReslicedNationalPatientProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -4396,9 +4394,9 @@ public async T.Task TestElementMappings() } - static void dumpMappings(ElementDefinition elem) => dumpMappings(elem.Mapping, $"Mappings for {elem.Path}:"); + private static void dumpMappings(ElementDefinition elem) => dumpMappings(elem.Mapping, $"Mappings for {elem.Path}:"); - static void dumpMappings(IList mappings, string header = null) + private static void dumpMappings(IList mappings, string header = null) { Debug.WriteLineIf(header != null, header); foreach (var mapping in mappings) @@ -4409,7 +4407,7 @@ static void dumpMappings(IList mappings, str // Ewout: type slices cannot contain renamed elements! - static StructureDefinition PatientNonTypeSliceProfile => new StructureDefinition() + private static StructureDefinition PatientNonTypeSliceProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4454,7 +4452,7 @@ public async T.Task TestPatientNonTypeSlice() } // Ewout: type slices cannot contain renamed elements! - static StructureDefinition ObservationSimpleQuantityProfile => new StructureDefinition() + private static StructureDefinition ObservationSimpleQuantityProfile => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -4615,7 +4613,7 @@ public async T.Task TestProfileConstraintsOnComplexExtensionChildren() // [WMR 20170424] For debugging ElementIdGenerator - static StructureDefinition TestQuestionnaireProfile => new StructureDefinition() + private static StructureDefinition TestQuestionnaireProfile => new StructureDefinition() { Type = FHIRAllTypes.Questionnaire.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Questionnaire), @@ -4789,7 +4787,7 @@ private static void assertElementIds(ElementDefinition elem, ElementDefinition b } - static StructureDefinition TestPatientTypeSliceProfile => new StructureDefinition() + private static StructureDefinition TestPatientTypeSliceProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4857,7 +4855,7 @@ public async T.Task TestElementIds_PatientWithTypeSlice() // [WMR 20170616] NEW - Test custom element IDs - static StructureDefinition TestSlicedPatientWithCustomIdProfile => new StructureDefinition() + private static StructureDefinition TestSlicedPatientWithCustomIdProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5092,7 +5090,7 @@ public async T.Task TestPatientWithAddress() Assert.AreEqual("land", baseElem.Alias.FirstOrDefault()); } - void dumpBaseDefId(StructureDefinition sd) + private void dumpBaseDefId(StructureDefinition sd) { Debug.Print("===== " + sd.Name); Debug.Print($"{"Path",50}| {"Base Path",49}| {"Base StructureDefinition",69}| {"Element Id",49}| {"Base Element Id",49}"); @@ -5113,12 +5111,12 @@ void dumpBaseDefId(StructureDefinition sd) // [WMR 20170424] For debugging ElementIdGenerator - const string PatientIdentifierProfileUri = @"http://example.org/fhir/StructureDefinition/PatientIdentifierProfile"; - const string PatientProfileWithIdentifierProfileUri = @"http://example.org/fhir/StructureDefinition/PatientProfileWithIdentifierProfile"; - const string PatientIdentifierTypeValueSetUri = @"http://example.org/fhir/ValueSet/PatientIdentifierTypeValueSet"; + private const string PatientIdentifierProfileUri = @"http://example.org/fhir/StructureDefinition/PatientIdentifierProfile"; + private const string PatientProfileWithIdentifierProfileUri = @"http://example.org/fhir/StructureDefinition/PatientProfileWithIdentifierProfile"; + private const string PatientIdentifierTypeValueSetUri = @"http://example.org/fhir/ValueSet/PatientIdentifierTypeValueSet"; // Identifier profile with valueset binding on child element Identifier.type - static StructureDefinition PatientIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientIdentifierProfile => new StructureDefinition() { Type = FHIRAllTypes.Identifier.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Identifier), @@ -5145,7 +5143,7 @@ void dumpBaseDefId(StructureDefinition sd) // Patient profile with type profile constraint on Patient.identifier // Snapshot should pick up the valueset binding on Identifier.type - static StructureDefinition PatientProfileWithIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientProfileWithIdentifierProfile => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5208,7 +5206,7 @@ public async T.Task TestTypeProfileWithChildElementBinding() Assert.IsFalse(nav.MoveToChild("type")); } - static StructureDefinition QuestionnaireResponseWithSlice => new StructureDefinition() + private static StructureDefinition QuestionnaireResponseWithSlice => new StructureDefinition() { Type = FHIRAllTypes.QuestionnaireResponse.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.QuestionnaireResponse), @@ -5314,7 +5312,7 @@ public async T.Task TestQRSliceChildrenBindings() // When expanding MyVitalSigns, the annotated base elements also include local diff constraints... WRONG! // As a result, Forge will not detect the existing local constraints (no yellow pen, excluded from output). - static StructureDefinition MyDerivedObservation => new StructureDefinition() + private static StructureDefinition MyDerivedObservation => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -5378,7 +5376,7 @@ public async T.Task TestDerivedObservation() Assert.AreEqual(coreMethodElem.Short, baseElem.Short); } - static StructureDefinition MyMoreDerivedObservation => new StructureDefinition() + private static StructureDefinition MyMoreDerivedObservation => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = MyDerivedObservation.Url, @@ -5460,7 +5458,7 @@ public async T.Task TestMoreDerivedObservation() } // [WMR 20170718] Test for slicing issue - static StructureDefinition MySlicedDocumentReference => new StructureDefinition() + private static StructureDefinition MySlicedDocumentReference => new StructureDefinition() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.DocumentReference), @@ -5555,7 +5553,7 @@ public async T.Task TestNamedSliceMinCardinality() // [WMR 20170718] NEW // Accept and handle derived profile constraints on existing slice entry in base profile - static StructureDefinition MySlicedBasePatient => new StructureDefinition() + private static StructureDefinition MySlicedBasePatient => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5582,7 +5580,7 @@ public async T.Task TestNamedSliceMinCardinality() } }; - static StructureDefinition MyMoreDerivedPatient => new StructureDefinition() + private static StructureDefinition MyMoreDerivedPatient => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = MySlicedBasePatient.Url, @@ -5682,7 +5680,7 @@ public async T.Task TestDosage() } } - static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new StructureDefinition() + private static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new StructureDefinition() { Type = FHIRAllTypes.MedicationStatement.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.MedicationStatement), @@ -5755,10 +5753,10 @@ public async T.Task TestSimpleQuantitySlice() // [WMR 20170925] BUG: Stefan Lang - Forge displays both valueString and value[x] // https://trello.com/c/XI8krV6j - const string SL_HumanNameTitleSuffixUri = @"http://example.org/fhir/StructureDefinition/SL-HumanNameTitleSuffix"; + private const string SL_HumanNameTitleSuffixUri = @"http://example.org/fhir/StructureDefinition/SL-HumanNameTitleSuffix"; // Extension on complex datatype HumanName - static StructureDefinition SL_HumanNameTitleSuffix => new StructureDefinition() + private static StructureDefinition SL_HumanNameTitleSuffix => new StructureDefinition() { Type = FHIRAllTypes.Extension.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Extension), @@ -5791,7 +5789,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on complex datatype HumanName with extension element - static StructureDefinition SL_HumanNameBasis => new StructureDefinition() + private static StructureDefinition SL_HumanNameBasis => new StructureDefinition() { Type = FHIRAllTypes.HumanName.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.HumanName), @@ -5821,7 +5819,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on Patient referencing custom HumanName datatype profile - static StructureDefinition SL_PatientBasis => new StructureDefinition() + private static StructureDefinition SL_PatientBasis => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5848,11 +5846,10 @@ public async T.Task TestSimpleQuantitySlice() } }; - - const string SL_NameSuffixValueSetUri = @"http://fhir.de/ValueSet/deuev/anlage-7-namenszusaetze"; + private const string SL_NameSuffixValueSetUri = @"http://fhir.de/ValueSet/deuev/anlage-7-namenszusaetze"; // Derived profile on Patient - static StructureDefinition SL_PatientDerived => new StructureDefinition() + private static StructureDefinition SL_PatientDerived => new StructureDefinition() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = SL_PatientBasis.Url, @@ -7251,7 +7248,7 @@ public async T.Task TestExtensionUrlFixedValueComplex() // fixedUri should inherit values from base profile // i.e. do NOT replace with canonical url of derived profile...! - static void AssertExtensionUrlChildElement(ElementDefinitionNavigator nav, string url) + private static void AssertExtensionUrlChildElement(ElementDefinitionNavigator nav, string url) { var bm = nav.Bookmark(); Assert.IsTrue(nav.MoveToChild("url")); @@ -7259,7 +7256,7 @@ static void AssertExtensionUrlChildElement(ElementDefinitionNavigator nav, strin nav.ReturnToBookmark(bm); } - static void AssertExtensionUrlElement(ElementDefinitionNavigator nav, string url) + private static void AssertExtensionUrlElement(ElementDefinitionNavigator nav, string url) { Assert.IsTrue(nav.Path.ToLowerInvariant().EndsWith("extension.url")); var fixedValue = nav.Current.Fixed; @@ -7368,7 +7365,7 @@ public async T.Task ShouldRespectMaxCardinalityFromBase() new SnapshotSource( new MultiResolver( new CachedResolver(new TestProfileArtifactSource()), - new ZipSource("specification.zip")))); + ZipSource.CreateValidationSource()))); var range = await cr.FindStructureDefinitionAsync("http://validationtest.org/fhir/StructureDefinition/RangeWithLowAsAQuantityWithUnlimitedRootCardinality"); var lowElement = range.Snapshot.Element.Single(e => e.Path == "Range.low"); diff --git a/src/Hl7.Fhir.Specification.Tests/StructureDefinitionWalkerTests.cs b/src/Hl7.Fhir.Specification.Tests/StructureDefinitionWalkerTests.cs index 8b749a1b44..b87157602c 100644 --- a/src/Hl7.Fhir.Specification.Tests/StructureDefinitionWalkerTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/StructureDefinitionWalkerTests.cs @@ -18,7 +18,7 @@ public static IAsyncResourceResolver CreateTestResolver() new SnapshotSource( new MultiResolver( new DirectorySource(@"TestData\validation"), - new ZipSource("specification.zip")))); + ZipSource.CreateValidationSource()))); } [ClassInitialize] diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index a87481b735..d1172ee1d3 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -698,7 +698,7 @@ public void ValidateCarePlan() var source = new MultiResolver( new DirectorySource(@"TestData\validation"), - new ZipSource("specification.zip")); + ZipSource.CreateValidationSource()); var ctx = new ValidationSettings() { @@ -1020,7 +1020,7 @@ public async T.Task TestPatientWithOrganization() // new DirectorySource(Path.Combine("TestData", "validation")), // new TestProfileArtifactSource(), memResolver, - new ZipSource("specification.zip")))); + ZipSource.CreateValidationSource()))); var ctx = new ValidationSettings() { @@ -1088,7 +1088,7 @@ public async System.Threading.Tasks.Task RunValueSetExpanderMultiThreaded() new BasicValidationTests.BundleExampleResolver(@"TestData\validation"), new DirectorySource(@"TestData\validation"), new TestProfileArtifactSource(), - new ZipSource("specification.zip"))); + ZipSource.CreateValidationSource())); var nrOfParrallelTasks = 50; var results = new ConcurrentBag(); diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/ProfileAssertionTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/ProfileAssertionTests.cs index 1e9e108fe0..f22ec24892 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/ProfileAssertionTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/ProfileAssertionTests.cs @@ -1,13 +1,10 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Validation; -using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using T=System.Threading.Tasks; using Xunit; +using T = System.Threading.Tasks; namespace Hl7.Fhir.Specification.Tests { @@ -25,7 +22,7 @@ public ValidationFixture() new BasicValidationTests.BundleExampleResolver(Path.Combine("TestData", "validation")), new DirectorySource(Path.Combine("TestData", "validation")), new TestProfileArtifactSource(), - new ZipSource("specification.zip"))); + ZipSource.CreateValidationSource())); var ctx = new ValidationSettings() { @@ -91,7 +88,7 @@ public async T.Task InitializationAndResolution() } -// We don't want to rewrite ProfileAssertion right now... + // We don't want to rewrite ProfileAssertion right now... #pragma warning disable CS0618 // Type or member is obsolete private StructureDefinition resolve(string uri) => _resolver.FindStructureDefinition(uri); #pragma warning restore CS0618 // Type or member is obsolete @@ -182,11 +179,11 @@ public void ResourceWithStatedProfiles() assertion.AddStatedProfile(ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation)); assertion.AddStatedProfile("http://validationtest.org/fhir/StructureDefinition/WeightHeightObservation"); assertion.AddStatedProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation"); - + var report = assertion.Validate(); Assert.True(report.Success); Assert.Equal(2, assertion.MinimalProfiles.Count()); - Assert.Equal( assertion.MinimalProfiles, assertion.StatedProfiles.Skip(1)); + Assert.Equal(assertion.MinimalProfiles, assertion.StatedProfiles.Skip(1)); assertion.SetDeclaredType(FHIRAllTypes.Procedure); report = assertion.Validate(); diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/ProfilePreprocessorTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/ProfilePreprocessorTests.cs index 0fdb5f4680..20013a7f79 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/ProfilePreprocessorTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/ProfilePreprocessorTests.cs @@ -19,12 +19,12 @@ public class ProfilePreprocessorTests public async T.Task RunSnapshotMultiThreaded() { // Arrange - var source = new CachedResolver(new ZipSource("specification.zip")); + var source = new CachedResolver(ZipSource.CreateValidationSource()); var generator = new SnapshotGenerator(source); OperationOutcome GenerateSnapshot(StructureDefinition sd) { -// We don't want to update ProfilePreprocessor right now + // We don't want to update ProfilePreprocessor right now #pragma warning disable CS0618 // Type or member is obsolete generator.Update(sd); #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs index 9d9811898a..55a341440c 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/TargetProfileValidationTests.cs @@ -71,7 +71,7 @@ public void AvoidsRedoingProfileValidation() new CachedResolver( new MultiResolver( new TestProfileArtifactSource(), - new ZipSource("specification.zip"), + ZipSource.CreateValidationSource(), visitResolver))); var validatorSettings = new ValidationSettings From 244d9e66b8a9804d28c8ea0804792f1b6e85267d Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Mon, 28 Mar 2022 16:16:16 +0200 Subject: [PATCH 10/55] Updated common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 732e09ab87..76a4e1be69 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 732e09ab8723f9e2c6075b1adf541f220a6eabb9 +Subproject commit 76a4e1be698545c742f66c89a050b540107b08cd From a08bec2caf55ad88ab064a7937992970ea8d3492 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Mon, 28 Mar 2022 17:48:15 +0200 Subject: [PATCH 11/55] bumped version to 3.8.2 --- common | 2 +- src/firely-net-sdk.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common b/common index 76a4e1be69..7ae1ce0651 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 76a4e1be698545c742f66c89a050b540107b08cd +Subproject commit 7ae1ce0651701082d6565768603460da950174d1 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index 8fab322cad..5bf49f4520 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -3,7 +3,7 @@ 3.8.2 - alpha + Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2021 Firely. Contains materials (C) HL7 International From bb3a195384ac7c81b0c938076a89f8f4ace92002 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Mon, 28 Mar 2022 17:48:16 +0200 Subject: [PATCH 12/55] bumped version to 3.8.2-stu3 --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 7ae1ce0651..76a4e1be69 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 7ae1ce0651701082d6565768603460da950174d1 +Subproject commit 76a4e1be698545c742f66c89a050b540107b08cd From 35fb12f9b486800cdb1455daad0093caaeff07ac Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Mon, 28 Mar 2022 22:28:30 +0200 Subject: [PATCH 13/55] Start new development phase: version 3.8.3-alpha --- common | 2 +- src/firely-net-sdk.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common b/common index 76a4e1be69..e9fd688bb2 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 76a4e1be698545c742f66c89a050b540107b08cd +Subproject commit e9fd688bb25b0883fb6df54923f767e0c6a20a98 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index 5bf49f4520..c87a0e7f46 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -2,8 +2,8 @@ - 3.8.2 - + 3.8.3 + alpha Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2021 Firely. Contains materials (C) HL7 International From b907b5cb6d798902cc1751a70374000420136aa5 Mon Sep 17 00:00:00 2001 From: Rob5045 Date: Mon, 11 Apr 2022 17:53:54 +0200 Subject: [PATCH 14/55] Updated unit test TestExpandBundleEntryResource. --- .../Snapshot/SnapshotGeneratorTest.cs | 110 ++++++++++++++---- 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 9671f3c2d1..62300f555a 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -6339,9 +6339,61 @@ public async T.Task TestExtensionValueReferenceRenaming() Assert.IsTrue(nav.MoveToChild("reference")); } - [TestMethod] - public async T.Task TestExpandBundleEntryResource() - { + /// + /// Test if a bundle entry resource is expanded. + /// + /// The resource type to add to the test bundle. + /// Optional canonical for the profile (to be used with FHIRAllTypes.Resource). + /// Optional element name to add to the differential (min = 1). + /// Flag indicating if the bundle entry resource should always be expanded. + [DataTestMethod] + [DataRow(FHIRAllTypes.Resource, "", "", false)] + [DataRow(FHIRAllTypes.Resource, "", "", true)] + [DataRow(FHIRAllTypes.Resource, "", "id", false)] + [DataRow(FHIRAllTypes.Resource, "", "id", true)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/List", "", false)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/List", "", true)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/List", "orderedBy", false)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/List", "orderedBy", true)] + [DataRow(FHIRAllTypes.List, "", "", false)] + [DataRow(FHIRAllTypes.List, "", "", true)] + [DataRow(FHIRAllTypes.List, "", "orderedBy", false)] + [DataRow(FHIRAllTypes.List, "", "orderedBy", true)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/Patient", "", false)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/Patient", "", true)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/Patient", "gender", false)] + [DataRow(FHIRAllTypes.Resource, "http://hl7.org/fhir/StructureDefinition/Patient", "gender", true)] + [DataRow(FHIRAllTypes.Patient, "", "", false)] + [DataRow(FHIRAllTypes.Patient, "", "", true)] + [DataRow(FHIRAllTypes.Patient, "", "gender", false)] + [DataRow(FHIRAllTypes.Patient, "", "gender", true)] + public async T.Task TestExpandBundleEntryResource(FHIRAllTypes fhirType, string profileCanonical, string differentialElement, bool alwaysExpand) + { + const string BundleEntryResource = "Bundle.entry.resource"; + + void OnBeforeExpandElement(object sender, SnapshotExpandElementEventArgs e) + { + if (e.Element.Path == BundleEntryResource) + { + if (!string.IsNullOrEmpty(differentialElement)) + { + Assert.IsTrue(e.MustExpand); + Assert.IsTrue(e.HasChildren); + } + else + { + Assert.AreEqual(e.MustExpand, e.HasChildren); + } + + if (alwaysExpand) + e.MustExpand = true; + } + else + { + Assert.AreEqual(e.MustExpand, e.HasChildren); + } + } + // Verify that the snapshot generator is capable of expanding Bundle.entry.resource, // if constrained to a resource type @@ -6349,21 +6401,21 @@ public async T.Task TestExpandBundleEntryResource() { Type = FHIRAllTypes.Bundle.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Bundle), - Name = "BundleWithList", - Url = @"http://example.org/fhir/StructureDefinition/BundleWithList", - //Derivation = StructureDefinition.TypeDerivationRule.Constraint, + Name = "BundleWithType", + Url = @"http://example.org/fhir/StructureDefinition/BundleWithType", Kind = StructureDefinition.StructureDefinitionKind.Resource, Differential = new StructureDefinition.DifferentialComponent() { Element = new List() { - new ElementDefinition("Bundle.entry.resource") + new ElementDefinition(BundleEntryResource) { Type = new List() { new ElementDefinition.TypeRefComponent() { - Code = FHIRAllTypes.List.GetLiteral() + Code = fhirType.GetLiteral(), + Profile = string.IsNullOrEmpty(profileCanonical) ? null : profileCanonical } } }, @@ -6371,27 +6423,31 @@ public async T.Task TestExpandBundleEntryResource() } }; + if (!string.IsNullOrEmpty(differentialElement)) + sd.Differential.Element.Add(new ElementDefinition($"{BundleEntryResource}.{differentialElement}") { Min = 1 }); + var resolver = new InMemoryProfileResolver(sd); var multiResolver = new MultiResolver(resolver, _testResolver); _generator = new SnapshotGenerator(multiResolver, _settings); Debug.Print("===== Prepare ===== "); - // Prepare standard snapshots for core Bundle & List + // Prepare standard snapshots for core Bundle & the specified fhir type var sdBundle = await _testResolver.FindStructureDefinitionForCoreTypeAsync(FHIRAllTypes.Bundle); Assert.IsNotNull(sdBundle); await _generator.UpdateAsync(sdBundle); Assert.IsTrue(sdBundle.HasSnapshot); - var sdList = await _testResolver.FindStructureDefinitionForCoreTypeAsync(FHIRAllTypes.List); - Assert.IsNotNull(sdList); - await _generator.UpdateAsync(sdList); - Assert.IsTrue(sdList.HasSnapshot); + var sdType = await _testResolver.FindStructureDefinitionForCoreTypeAsync(fhirType); + Assert.IsNotNull(sdType); + await _generator.UpdateAsync(sdType); + Assert.IsTrue(sdType.HasSnapshot); Debug.Print("===== Generate ===== "); // Generate custom snapshot for Bundle profile _generator.PrepareElement += elementHandler; + _generator.BeforeExpandElement += OnBeforeExpandElement; try { var (_, expanded) = await generateSnapshotAndCompare(sd); @@ -6405,34 +6461,38 @@ public async T.Task TestExpandBundleEntryResource() var elems = expanded.Snapshot.Element; - // [WMR 20180115] NEW - Use alternative (iterative) approach for full expansion + // The snapshot generator should fully expand resource children if the resource is different from + // the base resource or when we override the behaviour in the event BeforeExpandElement. var issues = _generator.Outcome?.Issue ?? new List(); - elems = await fullyExpand(elems, issues); Assert.AreEqual(0, issues.Count); - // Verify that Bundle.entry.resource : List was properly expanded - var pos = elems.FindIndex(e => e.Path == "Bundle.entry.resource"); + // Verify that Bundle.entry.resource : fhir type was properly expanded (or not) + var expectExpanded = !string.IsNullOrEmpty(differentialElement) || alwaysExpand; + var pos = elems.FindIndex(e => e.Path == BundleEntryResource); Assert.AreNotEqual(-1, pos); var elem = elems[pos]; - Assert.AreEqual(FHIRAllTypes.List.GetLiteral(), elem.Type.FirstOrDefault()?.Code); + Assert.AreEqual(fhirType.GetLiteral(), elem.Type.FirstOrDefault()?.Code); - // Verify that expanded child elements of Bundle.entry.resource contains all the elements in List snapshot - // [WMR 20180115] Full expansion will add additional grand children, not present in defaut List snapshot - var listElems = sdList.Snapshot.Element; + // Verify that expanded child elements of Bundle.entry.resource + var listElems = sdType.Snapshot.Element; for (int i = 1; i < listElems.Count; i++) { var listElem = listElems[i]; - var rebasedPath = ElementDefinitionNavigator.ReplacePathRoot(listElem.Path, "Bundle.entry.resource"); - // Verify that full Bundle expansion contains the default List snapshot element + var rebasedPath = ElementDefinitionNavigator.ReplacePathRoot(listElem.Path, BundleEntryResource); pos = elems.FindIndex(pos + 1, e => e.Path == rebasedPath); - Assert.AreNotEqual(-1, pos); + + // Verify bundle entry resource expansion in snapshot + if (expectExpanded) + Assert.AreNotEqual(-1, pos); // Should contain element + else + Assert.AreEqual(-1, pos); // Should not contain element } } finally { + _generator.BeforeExpandElement -= OnBeforeExpandElement; _generator.PrepareElement -= elementHandler; } - } // [WMR 20180115] From 4aa37eb07331453740c784f469ee3023c248f6d2 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 11:31:17 +0200 Subject: [PATCH 15/55] First attempt for signing the NuGet Packages --- build/azure-pipelines.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index bf864ce92b..bc7bfe4e98 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -16,6 +16,7 @@ trigger: name: $(date:yyyyMMdd)$(rev:.r) variables: +- group: CodeSigning - template: build-variables.yml - template: pipeline-variables.yml @@ -118,6 +119,13 @@ stages: configurationToPack: $(buildConfiguration) nobuild: true verbosityPack: Normal + - task: DotNetCoreCLI@2 + displayName: Code signing of packages + inputs: + command: custom + custom: nuget sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path $(FirelyCodeSignerCertificate) --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) + + - task: PublishBuildArtifacts@1 displayName: Publish Artifact inputs: From 4e6c4bdd39a06f1751dde11da1561979e002e4b2 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 11:45:57 +0200 Subject: [PATCH 16/55] try again --- build/azure-pipelines.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index bc7bfe4e98..18b641873c 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -119,12 +119,13 @@ stages: configurationToPack: $(buildConfiguration) nobuild: true verbosityPack: Normal + - task: DotNetCoreCLI@2 displayName: Code signing of packages inputs: command: custom - custom: nuget sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path $(FirelyCodeSignerCertificate) --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) - + custom: nuget + arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path $(FirelyCodeSignerCertificate) --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) - task: PublishBuildArtifacts@1 displayName: Publish Artifact From 4de75e5864bbbbd2eba35ad9cc414503e34c483f Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 12:04:34 +0200 Subject: [PATCH 17/55] Use .NET Core 6.0.x --- build/build-variables.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build-variables.yml b/build/build-variables.yml index c0d69fe96d..b4b5632cf7 100644 --- a/build/build-variables.yml +++ b/build/build-variables.yml @@ -3,7 +3,7 @@ # Variables used during builds. variables: - NET_CORE_SDK: 5.0.x + NET_CORE_SDK: 6.0.x DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true TEST_TARGETFRAMEWORK: net5.0 buildConfiguration: Release \ No newline at end of file From 4450a3f2011b9bc09207dcc0735e9488f28742e1 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 12:31:59 +0200 Subject: [PATCH 18/55] Adding a bit of Powershell --- build/azure-pipelines.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 18b641873c..0c2096744c 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -119,13 +119,27 @@ stages: configurationToPack: $(buildConfiguration) nobuild: true verbosityPack: Normal - + + - powershell: | + #Convert the Secure password that's presented as plain text back into a secure string + $pwd = ConvertTo-SecureString -String $(CodeSigningPassword) -Force -AsPlainText + + #Create PFX file from Certificate Variable + New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) + + #Import the PFX certificate from the newly created file and password. Read the thumbprint into variable + $Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint + + Write-Host $Thumbprint + + #Rest of Script below or set environment variable for rest of Pipeline + Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" - task: DotNetCoreCLI@2 displayName: Code signing of packages inputs: command: custom custom: nuget - arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path $(FirelyCodeSignerCertificate) --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) + arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) - task: PublishBuildArtifacts@1 displayName: Publish Artifact From 820115424627d43f9f5facda6cb6a64e51951587 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 12:48:40 +0200 Subject: [PATCH 19/55] Create only the pfx file --- build/azure-pipelines.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 0c2096744c..5e1ca8943f 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -122,18 +122,18 @@ stages: - powershell: | #Convert the Secure password that's presented as plain text back into a secure string - $pwd = ConvertTo-SecureString -String $(CodeSigningPassword) -Force -AsPlainText + #$pwd = ConvertTo-SecureString -String $(CodeSigningPassword) -Force -AsPlainText #Create PFX file from Certificate Variable New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) #Import the PFX certificate from the newly created file and password. Read the thumbprint into variable - $Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint + #$Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint - Write-Host $Thumbprint + #Write-Host $Thumbprint #Rest of Script below or set environment variable for rest of Pipeline - Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" + #Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" - task: DotNetCoreCLI@2 displayName: Code signing of packages inputs: From 558278e5d6dd80f4b66ce7b66e02d8ed24d14579 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 14:26:44 +0200 Subject: [PATCH 20/55] another attempt --- build/azure-pipelines.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 5e1ca8943f..cb8f368ca3 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -122,24 +122,22 @@ stages: - powershell: | #Convert the Secure password that's presented as plain text back into a secure string - #$pwd = ConvertTo-SecureString -String $(CodeSigningPassword) -Force -AsPlainText + $pwd = ConvertTo-SecureString -String "$(CodeSigningPassword)" -Force -AsPlainText + + Write-Host $(CodeSigningPassword) + Write-Host $pwd #Create PFX file from Certificate Variable New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) - #Import the PFX certificate from the newly created file and password. Read the thumbprint into variable - #$Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint - - #Write-Host $Thumbprint - #Rest of Script below or set environment variable for rest of Pipeline - #Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" + Write-Host "##vso[task.setvariable variable=certpassword]$pwd" - task: DotNetCoreCLI@2 displayName: Code signing of packages inputs: command: custom custom: nuget - arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password $(CodeSigningPassword) + arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password $(certpassword) - task: PublishBuildArtifacts@1 displayName: Publish Artifact From f5fe1cdfce1a72c889a77896b9efd00b055a6452 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 14:45:36 +0200 Subject: [PATCH 21/55] Yes, and again --- build/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index cb8f368ca3..951db284af 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -137,7 +137,7 @@ stages: inputs: command: custom custom: nuget - arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password $(certpassword) + arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password "$(CodeSigningPassword)" - task: PublishBuildArtifacts@1 displayName: Publish Artifact From b94d24802825099b04bab7c70c74086bfb51a29e Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 15:02:22 +0200 Subject: [PATCH 22/55] add pfx to publising --- build/azure-pipelines.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 951db284af..8c054d5d98 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -128,16 +128,16 @@ stages: Write-Host $pwd #Create PFX file from Certificate Variable - New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) + New-Item $(Build.ArtifactStagingDirectory)\Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) #Rest of Script below or set environment variable for rest of Pipeline Write-Host "##vso[task.setvariable variable=certpassword]$pwd" - - task: DotNetCoreCLI@2 - displayName: Code signing of packages - inputs: - command: custom - custom: nuget - arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password "$(CodeSigningPassword)" + #- task: DotNetCoreCLI@2 + # displayName: Code signing of packages + # inputs: + # command: custom + # custom: nuget + # arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password "$(CodeSigningPassword)" - task: PublishBuildArtifacts@1 displayName: Publish Artifact From 45013dcc235f375dafb1c9a8a6251a900203d2b8 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 15:35:33 +0200 Subject: [PATCH 23/55] I think this is it --- build/azure-pipelines.yml | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 8c054d5d98..1103a8176e 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -124,20 +124,29 @@ stages: #Convert the Secure password that's presented as plain text back into a secure string $pwd = ConvertTo-SecureString -String "$(CodeSigningPassword)" -Force -AsPlainText - Write-Host $(CodeSigningPassword) - Write-Host $pwd - #Create PFX file from Certificate Variable - New-Item $(Build.ArtifactStagingDirectory)\Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) + New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) + + #Import the PFX certificate from the newly created file and password. Read the thumbprint into variable + $Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint + + Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" + + #Remove the pfx file, the certificate is now imported + Remove-Item Temp-Certificate.pfx + displayName: 'Import Code Signing certificate' - #Rest of Script below or set environment variable for rest of Pipeline - Write-Host "##vso[task.setvariable variable=certpassword]$pwd" - #- task: DotNetCoreCLI@2 - # displayName: Code signing of packages - # inputs: - # command: custom - # custom: nuget - # arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-path Temp-Certificate.pfx --timestamper http://timestamp.digicert.com --certificate-password "$(CodeSigningPassword)" + - task: DotNetCoreCLI@2 + displayName: 'Code signing of packages' + inputs: + command: custom + custom: nuget + arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-fingerprint $(Thumbprint) --timestamper http://timestamp.digicert.com + + - powershell: | + #Delete the certificate by thumbprint, so it cannot be used elsewhere. + Get-ChildItem Cert:\CurrentUser\My\$(Thumbprint) | Remove-Item + displayName: 'Remove the certificate from cert store' - task: PublishBuildArtifacts@1 displayName: Publish Artifact From 9fab770c0a7b81b6091de6da1566ced61df031e8 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 15:59:18 +0200 Subject: [PATCH 24/55] Use the general template for signing now --- build/azure-pipelines.yml | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 1103a8176e..2c37a062c2 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -119,35 +119,13 @@ stages: configurationToPack: $(buildConfiguration) nobuild: true verbosityPack: Normal - - - powershell: | - #Convert the Secure password that's presented as plain text back into a secure string - $pwd = ConvertTo-SecureString -String "$(CodeSigningPassword)" -Force -AsPlainText - - #Create PFX file from Certificate Variable - New-Item Temp-Certificate.pfx -Value $(FirelyCodeSignerCertificate) - - #Import the PFX certificate from the newly created file and password. Read the thumbprint into variable - $Thumbprint = (Import-PfxCertificate -CertStoreLocation Cert:\CurrentUser\My -FilePath Temp-Certificate.pfx -Password $pwd).Thumbprint - Write-Host "##vso[task.setvariable variable=Thumbprint]$Thumbprint" + - template: templates/codesign-nuget-packages.yml + parameters: + certificateValue: $(FirelyCodeSignerCertificate) + certificatePasswordValue: $(CodeSigningPassword) + packagePaths: $(Build.ArtifactStagingDirectory)\*.nupkg - #Remove the pfx file, the certificate is now imported - Remove-Item Temp-Certificate.pfx - displayName: 'Import Code Signing certificate' - - - task: DotNetCoreCLI@2 - displayName: 'Code signing of packages' - inputs: - command: custom - custom: nuget - arguments: sign $(Build.ArtifactStagingDirectory)\*.nupkg --certificate-fingerprint $(Thumbprint) --timestamper http://timestamp.digicert.com - - - powershell: | - #Delete the certificate by thumbprint, so it cannot be used elsewhere. - Get-ChildItem Cert:\CurrentUser\My\$(Thumbprint) | Remove-Item - displayName: 'Remove the certificate from cert store' - - task: PublishBuildArtifacts@1 displayName: Publish Artifact inputs: From 16b0ff47e1469bdb6a43de833cb4401650b6f675 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 16:03:16 +0200 Subject: [PATCH 25/55] Correct reference to general template --- build/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 2c37a062c2..ef44ea0e44 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -120,7 +120,7 @@ stages: nobuild: true verbosityPack: Normal - - template: templates/codesign-nuget-packages.yml + - template: codesign-nuget-packages.yml@templates parameters: certificateValue: $(FirelyCodeSignerCertificate) certificatePasswordValue: $(CodeSigningPassword) From fd3d6acd865a0d84f8548ab910790563f6aed2bf Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 16:46:36 +0200 Subject: [PATCH 26/55] Adding the flag /p:ContinuousIntegrationBuild=true during build --- build/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ef44ea0e44..7fc09d48e6 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -72,7 +72,7 @@ stages: inputs: command: build projects: ./Hl7.Fhir.sln - arguments: --configuration $(buildConfiguration) --no-restore + arguments: --configuration $(buildConfiguration) --no-restore /p:ContinuousIntegrationBuild=true - task: DotNetCoreCLI@2 displayName: Create Test artifacts From ecbc26ce90a5ce0f1e16c0a26f93dc29764cd75e Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 12 Apr 2022 18:16:34 +0200 Subject: [PATCH 27/55] Added Sourcelink for enabling anyone building NuGet libraries to provide source debugging --- common | 2 +- src/firely-net-sdk.props | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/common b/common index e9fd688bb2..604283a065 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit e9fd688bb25b0883fb6df54923f767e0c6a20a98 +Subproject commit 604283a0659eb47b700b5fcdd3274aa3650b1f8c diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index c87a0e7f46..bc5ce64a68 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -13,6 +13,8 @@ icon-fhir-32.png See https://github.com/FirelyTeam/firely-net-sdk/releases LICENSE + true + true false @@ -26,6 +28,10 @@ + + + + From 33fda0349bc327b915705aeb4b754bb042bb9937 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 14 Apr 2022 15:43:15 +0200 Subject: [PATCH 28/55] Use a workflow for automatic add issues to the project --- .github/workflows/AddIssuesToProject.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/AddIssuesToProject.yml diff --git a/.github/workflows/AddIssuesToProject.yml b/.github/workflows/AddIssuesToProject.yml new file mode 100644 index 0000000000..2a64af475e --- /dev/null +++ b/.github/workflows/AddIssuesToProject.yml @@ -0,0 +1,16 @@ +name: Add isuses to project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/FirelyTeam/projects/6 + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} \ No newline at end of file From 8ef255a33f8142539ef87183f491d08371e3e035 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 20 Apr 2022 13:51:47 +0200 Subject: [PATCH 29/55] Update common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 604283a065..daca988a75 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 604283a0659eb47b700b5fcdd3274aa3650b1f8c +Subproject commit daca988a7547659811754dfb3a6a51b6c55bd0a5 From 20b4b91c01ca4f26ba388b06ad772ddd3263ef0b Mon Sep 17 00:00:00 2001 From: brian_pos Date: Mon, 11 Apr 2022 17:25:21 +1000 Subject: [PATCH 30/55] #2029 indicate to the HttpClient that the handler provided can be disposed when it is done with it. # Conflicts: # src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs --- .../Rest/FhirClientTests.cs | 169 +++++++++++++----- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 2 +- 2 files changed, 126 insertions(+), 45 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 11d3285e00..8a2b733cd9 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -1,7 +1,7 @@ -/* +/* * Copyright (c) 2014, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ @@ -38,13 +38,14 @@ public class FhirClientTests //public static Uri testEndpoint = new Uri("http://fhirtest.uhn.ca/baseDstu3"); //public static Uri testEndpoint = new Uri("http://localhost:49911/fhir"); //public static Uri testEndpoint = new Uri("http://sqlonfhir-stu3.azurewebsites.net/fhir"); - public static Uri testEndpoint = new Uri("https://server.fire.ly/r3"); + public static Uri testEndpoint = new Uri("https://server.fire.ly/r4"); - //public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:49911/fhir"); + //public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:49911/fhir"); public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:4080"); //public static Uri _endpointSupportingSearchUsingPost = new Uri("https://vonk.fire.ly/r3"); - public static Uri TerminologyEndpoint = new Uri("https://stu3.ontoserver.csiro.au/fhir"); + public static Uri TerminologyEndpoint = new Uri("https://r4.ontoserver.csiro.au/fhir"); + // public static Uri TerminologyEndpoint = new Uri("http://test.fhir.org/r4"); private static string patientId = "pat1" + ModelInfo.Version; private static string locationId = "loc1" + ModelInfo.Version; @@ -315,7 +316,7 @@ public async T.Task ReadHttpClient() await testReadClientAsync(client); } } - + private async T.Task testReadClientAsync(BaseFhirClient client) { var loc = client.Read("Location/" + locationId); @@ -387,7 +388,7 @@ private void testReadRelative(BaseFhirClient client) public void ReadRelativeAsync() { FhirClient client = new FhirClient(testEndpoint); - testRelativeAsyncClient(client); + testRelativeAsyncClient(client); } [TestMethod, TestCategory("FhirClient")] @@ -611,6 +612,44 @@ private void testSearchAsyncHttpClient(BaseFhirClient client) Assert.IsNotNull(result); Assert.IsTrue(result.Entry.Count > 0); } + + public void SearchAsyncHttpClient() + { + using(TestClient client = new TestClient(testEndpoint)) + { + Bundle result; + + result = client.SearchAsync().Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count() > 10, "Test should use testdata with more than 10 reports"); + + result = client.SearchAsync(pageSize: 10).Result; + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count <= 10); + + var withSubject = + result.Entry.ByResourceType().FirstOrDefault(dr => dr.Resource.Subject != null); + Assert.IsNotNull(withSubject, "Test should use testdata with a report with a subject"); + + ResourceIdentity ri = new ResourceIdentity(withSubject.Id); + + result = client.SearchByIdAsync(ri.Id, + includes: new string[] { "DiagnosticReport.subject" }).Result; + Assert.IsNotNull(result); + + Assert.AreEqual(2, result.Entry.Count); // should have subject too + + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(DiagnosticReport).GetCollectionName())); + Assert.IsNotNull(result.Entry.Single(entry => new ResourceIdentity(entry.Id).Collection == + typeof(Patient).GetCollectionName())); + + result = client.SearchAsync(new string[] { "name=Everywoman", "name=Eve" }).Result; + + Assert.IsNotNull(result); + Assert.IsTrue(result.Entry.Count > 0); + } + } #endif @@ -912,7 +951,7 @@ private static void testCreateEditDeleteAsync(BaseFhirClient client) Name = "Furore", Identifier = new List { new Identifier("http://hl7.org/test/1", "3141") }, Telecom = new List { new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Value = "+31-20-3467171" } } - }; + }; var fe = client.CreateAsync(furore).Result; @@ -926,9 +965,9 @@ private static void testCreateEditDeleteAsync(BaseFhirClient client) var fe2 = client.UpdateAsync(fe).Result; Assert.IsNotNull(fe2); - Assert.AreEqual(fe.Id, fe2.Id); + Assert.AreEqual(fe.Id, fe2.Id); + - fe.Identifier.Add(new Identifier("http://hl7.org/test/3", "3141592")); var fe3 = client.UpdateAsync(fe2).Result; @@ -952,7 +991,7 @@ private static void testCreateEditDeleteAsync(BaseFhirClient client) /// - /// This test will fail if the system records AuditEvents + /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? @@ -964,7 +1003,7 @@ public async T.Task History() /// - /// This test will fail if the system records AuditEvents + /// This test will fail if the system records AuditEvents /// and counts them in the WholeSystemHistory /// [TestMethod, TestCategory("FhirClient"), TestCategory("IntegrationTest"), Ignore] // Keeps on failing periodically. Grahames server? @@ -998,7 +1037,7 @@ private async T.Task testHistoryAsync(BaseFhirClient client) history = client.TypeHistory("Patient", timestampBeforeCreationAndDeletions.ToUniversalTime()); Assert.IsNotNull(history); DebugDumpBundle(history); - Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. + Assert.AreEqual(4, history.Entry.Count()); // there's a race condition here, sometimes this is 5. Assert.AreEqual(3, history.Entry.Where(entry => entry.Resource != null).Count()); Assert.AreEqual(1, history.Entry.Where(entry => entry.IsDeleted()).Count()); @@ -1184,8 +1223,8 @@ private void verifyMeta(Meta meta, bool hasNew, int key) [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestSearchUsingPostMultipleIncludesShouldNotThrowArgumentException() { - // This test case proves issue https://github.com/FirelyTeam/firely-net-sdk/issues/1206 is fixed. - // Previoulsly EntryToHttpExtensions.setBodyAndContentType used a Dictionary which required the + // This test case proves issue https://github.com/FirelyTeam/firely-net-sdk/issues/1206 is fixed. + // Previoulsly EntryToHttpExtensions.setBodyAndContentType used a Dictionary which required the // name part of the parameters to be unique. // Fixed by using IEnumerable> instead of Dictionary var client = new LegacyFhirClient(testEndpoint); @@ -1197,8 +1236,8 @@ public void TestSearchUsingPostMultipleIncludesShouldNotThrowArgumentException() [TestCategory("FhirClient"), TestCategory("IntegrationTest")] public void TestSearchUsingPostMultipleIncludesShouldNotThrowArgumentExceptionHttpClient() { - // This test case proves issue https://github.com/FirelyTeam/firely-net-sdk/issues/1206 is fixed. - // Previoulsly EntryToHttpExtensions.setBodyAndContentType used a Dictionary which required the + // This test case proves issue https://github.com/FirelyTeam/firely-net-sdk/issues/1206 is fixed. + // Previoulsly EntryToHttpExtensions.setBodyAndContentType used a Dictionary which required the // name part of the parameters to be unique. // Fixed by using IEnumerable> instead of Dictionary var client = new FhirClient(testEndpoint); @@ -1347,43 +1386,85 @@ public void CallsCallbacks() public void CallsCallbacksHttpClient() { using (var handler = new HttpClientEventHandler()) - using (FhirClient client = new FhirClient(testEndpoint, messageHandler: handler)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + using (FhirClient client = new FhirClient(testEndpoint, messageHandler: handler)) + { + client.Settings.ParserSettings.AllowUnrecognizedEnums = true; - bool calledBefore = false; - HttpStatusCode? status = null; - byte[] body = null; - byte[] bodyOut = null; + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; - handler.OnBeforeRequest += (sender, e) => - { - calledBefore = true; - bodyOut = e.Body; - }; + handler.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; + + handler.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/" + patientId); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); - handler.OnAfterResponse += (sender, e) => + var bodyText = HttpUtil.DecodeBody(body, Encoding.UTF8); + + Assert.IsTrue(bodyText.Contains("("Patient/" + patientId); - Assert.IsTrue(calledBefore); - Assert.IsNotNull(status); - Assert.IsNotNull(body); + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; - var bodyText = HttpUtil.DecodeBody(body, Encoding.UTF8); + handler.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; - Assert.IsTrue(bodyText.Contains(" + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/" + patientId); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); + + var bodyText = HttpUtil.DecodeBody(body, Encoding.UTF8); - calledBefore = false; - client.Update(pat); // create cannot be called with an ID (which was retrieved) - Assert.IsTrue(calledBefore); - Assert.IsNotNull(bodyOut); + Assert.IsTrue(bodyText.Contains(" Date: Tue, 12 Apr 2022 09:39:20 +1000 Subject: [PATCH 31/55] #2029 code comment and also move the common pointer up to a newer version that includes the default parameter for the disposeHandler (even though we pass it throuth) --- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index 54c6e609b4..9d771c0e10 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -1,7 +1,7 @@ -/* +/* * Copyright (c) 2014, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ @@ -21,11 +21,14 @@ public partial class FhirClient : BaseFhirClient { //disables warning that OnBeforeRequest and OnAfterResponse are never used. #pragma warning disable CS0067 - + /// /// Creates a new client using a default endpoint /// If the endpoint does not end with a slash (/), it will be added. /// + /// + /// If the messageHandler is provided then it must be disposed by the caller + /// /// /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically @@ -122,7 +125,7 @@ public bool ReturnFullResource } /// - /// Should calls to Create, Update and transaction operations return the whole updated content, + /// Should calls to Create, Update and transaction operations return the whole updated content, /// or an OperationOutcome? /// /// Refer to specification section 2.1.0.5 (Managing Return Content) @@ -157,7 +160,7 @@ public bool PreferCompressedResponses set => Settings.PreferCompressedResponses = value; } /// - /// Compress any Request bodies + /// Compress any Request bodies /// (warning, if a server does not handle compressed requests you will get a 415 response) /// [Obsolete("Use the FhirClient.Settings property or the settings argument in the constructor instead")] @@ -201,6 +204,6 @@ protected override void Dispose(bool disposing) } } - + } From 7cfde367907f555e09fd9d975a74b50ed153f15b Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Wed, 20 Apr 2022 14:36:42 +0200 Subject: [PATCH 32/55] added test to reproduce stack overflow exception --- .../Hl7.Fhir.Specification.Tests.csproj | 40 + .../absolute-contentReference-stu3.xml | 3723 +++++++++++++++++ .../Validation/BasicValidationTests.cs | 40 + 3 files changed, 3803 insertions(+) create mode 100644 src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml diff --git a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj index 8db23528ce..f2782d1c1e 100644 --- a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj +++ b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj @@ -31,6 +31,46 @@ PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml b/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml new file mode 100644 index 0000000000..ddb72410b0 --- /dev/null +++ b/src/Hl7.Fhir.Specification.Tests/TestData/validation/absolute-contentReference-stu3.xml @@ -0,0 +1,3723 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index d1172ee1d3..bad4212a42 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -1270,6 +1270,46 @@ public void ValidateWithTargetProfileAndChildDefinitions() } + [Fact] + public void ValidateAbsoluteContentReferences() + { + //prepare + var resolver = new MultiResolver( + new DirectorySource(@"TestData\validation"), + ZipSource.CreateValidationSource()); + + var validator = new Validator(new ValidationSettings() { ResourceResolver = resolver, GenerateSnapshot = false }); + + var questionnaire = new Questionnaire() + { + Meta = new Meta() + { + Profile = new string[] { "https://firely-sdk.org/fhir/StructureDefinition/AbsoluteContentReference" } + }, + Status = PublicationStatus.Active, + Item = new List + { + new Questionnaire.ItemComponent() + { + LinkId = "1", + Type = Questionnaire.QuestionnaireItemType.Boolean, + Item = new List + { + new Questionnaire.ItemComponent() + { + LinkId = "1", + Type = Questionnaire.QuestionnaireItemType.String + } + } + } + } + }; + + var outcome = validator.Validate(questionnaire); + Assert.True(outcome.Success); + } + + private class ClearSnapshotResolver : IResourceResolver { private readonly IResourceResolver _resolver; From 730262d2628e04a9248f2b8af90d8930763a3ab5 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 20 Apr 2022 14:42:54 +0200 Subject: [PATCH 33/55] add r3 uri --- src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 8a2b733cd9..86c2eed034 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -38,14 +38,14 @@ public class FhirClientTests //public static Uri testEndpoint = new Uri("http://fhirtest.uhn.ca/baseDstu3"); //public static Uri testEndpoint = new Uri("http://localhost:49911/fhir"); //public static Uri testEndpoint = new Uri("http://sqlonfhir-stu3.azurewebsites.net/fhir"); - public static Uri testEndpoint = new Uri("https://server.fire.ly/r4"); + public static Uri testEndpoint = new Uri("https://server.fire.ly/r3"); //public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:49911/fhir"); public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:4080"); //public static Uri _endpointSupportingSearchUsingPost = new Uri("https://vonk.fire.ly/r3"); public static Uri TerminologyEndpoint = new Uri("https://r4.ontoserver.csiro.au/fhir"); - // public static Uri TerminologyEndpoint = new Uri("http://test.fhir.org/r4"); + // public static Uri TerminologyEndpoint = new Uri("http://test.fhir.org/r3"); private static string patientId = "pat1" + ModelInfo.Version; private static string locationId = "loc1" + ModelInfo.Version; From b19240e834e4583d5b771b71a35f761c1623c4b2 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 20 Apr 2022 14:56:57 +0200 Subject: [PATCH 34/55] Use stu3 version of the ontoserver --- src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 86c2eed034..4aef5badcd 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -44,7 +44,7 @@ public class FhirClientTests public static Uri _endpointSupportingSearchUsingPost = new Uri("http://localhost:4080"); //public static Uri _endpointSupportingSearchUsingPost = new Uri("https://vonk.fire.ly/r3"); - public static Uri TerminologyEndpoint = new Uri("https://r4.ontoserver.csiro.au/fhir"); + public static Uri TerminologyEndpoint = new Uri("https://stu3.ontoserver.csiro.au/fhir"); // public static Uri TerminologyEndpoint = new Uri("http://test.fhir.org/r3"); private static string patientId = "pat1" + ModelInfo.Version; From 57ec84be376fdf3976fe133e1d354cc6206bbeaa Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 20 Apr 2022 16:10:30 +0200 Subject: [PATCH 35/55] Initial fix - needs testing --- .../Snapshot/SnapshotGeneratorTest.cs | 40 ++++++----- .../Navigation/ElementDefinitionNavigator.cs | 41 +++++------ .../Navigation/ProfileReference.cs | 39 +++++++---- .../Navigation/StructureDefinitionWalker.cs | 10 ++- .../Specification/Snapshot/ElementMatcher.cs | 46 ++++++------ .../Snapshot/SnapshotGenerator.cs | 70 +++++++------------ .../Source/ResourceResolverExtensions.cs | 11 +-- .../StructureDefinitionSummaryProvider.cs | 33 ++++----- .../ElementDefinitionNavigatorExtensions.cs | 34 +++++++++ .../Validation/Validator.cs | 38 +++++----- 10 files changed, 185 insertions(+), 177 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 9671f3c2d1..80992316cf 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -45,7 +45,7 @@ public class SnapshotGeneratorTest2 private ZipSource _zipSource; private CachedResolver _testResolver; private TimingSource _source; - private readonly SnapshotGeneratorSettings _settings = new SnapshotGeneratorSettings() + private readonly SnapshotGeneratorSettings _settings = new() { // Throw on unresolved profile references; must include in TestData folder GenerateSnapshotForExternalProfiles = true, @@ -69,7 +69,7 @@ public void Setup() _testResolver = new CachedResolver(new MultiResolver(_zipSource, _source)); } - private StructureDefinition CreateStructureDefinition(string url, params ElementDefinition[] elements) + private StructureDefinition createStructureDefinition(string url, params ElementDefinition[] elements) { return new StructureDefinition { @@ -156,7 +156,7 @@ public async T.Task OverriddenNestedStructureDefinitionLists() var code = "someCode"; var discriminatorPath = "system"; - var baseSD = CreateStructureDefinition(baseCanonical, + var baseSD = createStructureDefinition(baseCanonical, new ElementDefinition { Path = "Practitioner.identifier", @@ -184,7 +184,7 @@ public async T.Task OverriddenNestedStructureDefinitionLists() } }); - var derivedSD = CreateStructureDefinition("http://yourdomain.org/fhir/StructureDefinition/Derived", + var derivedSD = createStructureDefinition("http://yourdomain.org/fhir/StructureDefinition/Derived", new ElementDefinition { Path = "Practitioner.identifier", @@ -1611,7 +1611,7 @@ private void dumpIssues(List issues) [Conditional("DEBUG")] private void dumpIssue(OperationOutcome.IssueComponent issue, int index) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.AppendFormat("* Issue #{0}: Severity = '{1}' Code = '{2}'", index, issue.Severity, issue.Code); if (issue.Details != null) { @@ -2131,7 +2131,7 @@ public async T.Task TestBaseAnnotations_ExtensionDefinition() // [WMR 20170714] NEW // Annotated Base Element for backbone elements is not included in base structuredefinition ? - private static StructureDefinition MyTestObservation => new StructureDefinition() + private static StructureDefinition MyTestObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -2922,7 +2922,7 @@ public async T.Task FindComplexTestExtensions() } // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition ObservationTypeSliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeSliceProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3070,7 +3070,7 @@ public async T.Task TestUnresolvedBaseProfile() assertIssue(outcome.Issue[0], Issue.UNAVAILABLE_REFERENCED_PROFILE, profile.BaseDefinition); } - private static StructureDefinition ObservationTypeResliceProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeResliceProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ObservationTypeSliceProfile.Url, @@ -3151,7 +3151,7 @@ public async T.Task TestTypeReslicing() } // Choice type constraint, with element renaming - private static StructureDefinition ObservationTypeConstraintProfile => new StructureDefinition() + private static StructureDefinition ObservationTypeConstraintProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -3246,7 +3246,7 @@ public async T.Task TestInvalidChoiceTypeConstraints() assertIssue(outcome.Issue[0], SnapshotGenerator.PROFILE_ELEMENTDEF_INVALID_CHOICE_CONSTRAINT); } - private static StructureDefinition ClosedExtensionSliceObservationProfile => new StructureDefinition() + private static StructureDefinition ClosedExtensionSliceObservationProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -6665,7 +6665,7 @@ public async T.Task TestAuPatientDerived2() // [WMR 20180410] Add unit tests for content references - public StructureDefinition QuestionnaireWithNestedItems = new StructureDefinition() + public StructureDefinition QuestionnaireWithNestedItems = new() { Type = FHIRAllTypes.Questionnaire.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Questionnaire), @@ -7179,7 +7179,7 @@ public async T.Task TestExtensionUrlFixedValueSimple() [TestMethod] public async T.Task TestExtensionUrlFixedValueComplex() { - StructureDefinition ComplexTestExtension = new StructureDefinition() + StructureDefinition ComplexTestExtension = new() { Type = FHIRAllTypes.Extension.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Extension), @@ -7418,14 +7418,15 @@ await act [TestMethod] public async T.Task SnapshotSucceedsWithExtendedVariantElementDef() { - var structureDef = new StructureDefinition(); - structureDef.BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Observation"; - structureDef.Type = "Observation"; - structureDef.Url = "http://some.canonical"; - - structureDef.Differential = new StructureDefinition.DifferentialComponent + var structureDef = new StructureDefinition { - Element = new System.Collections.Generic.List{ + BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Observation", + Type = "Observation", + Url = "http://some.canonical", + + Differential = new StructureDefinition.DifferentialComponent + { + Element = new System.Collections.Generic.List{ new ElementDefinition { ElementId = "Observation.value[x].extension", @@ -7458,6 +7459,7 @@ public async T.Task SnapshotSucceedsWithExtendedVariantElementDef() } } } + } }; diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index eef92e5872..cb8e19576b 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -6,13 +6,13 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; -using Hl7.Fhir.Model; -using System.Diagnostics; -using Hl7.Fhir.Utility; namespace Hl7.Fhir.Specification.Navigation { @@ -282,17 +282,17 @@ public bool JumpToNameReference(string nameReference) { if (Count == 0) return false; + var profileRef = ProfileReference.Parse(nameReference); + + // Namereferences should normally look like '#Questionnaire.item', but in snapshots for profiles, these can also + // be absolute urls pointing to an external (core) StructureDefinition, but we cannot handle those here as + // that should be dealt with by the caller. + if (profileRef.IsAbsolute && profileRef.CanonicalUrl != StructureDefinition.Url) + throw new NotSupportedException("Can only jump to local nameReferences, not " + nameReference); + for (int pos = 0; pos < Count; pos++) { - if (nameReference.StartsWith("#")) - { - if (Elements[pos].ElementId == nameReference.TrimStart('#')) - { - OrdinalPosition = pos; - return true; - } - } - else if (Elements[pos].ContentReference == nameReference) + if (Elements[pos].ElementId == "#" + profileRef.ElementName) { OrdinalPosition = pos; return true; @@ -309,17 +309,9 @@ public bool JumpToNameReference(string nameReference) // //---------------------------------- - public Bookmark Bookmark() => new Bookmark(Current, OrdinalPosition); + public Bookmark Bookmark() => new(Current, OrdinalPosition); - public bool IsAtBookmark(Bookmark bookmark) - { - var elem = bookmark.data as ElementDefinition; - if (elem == null) - { - return OrdinalPosition == null; - } - return object.ReferenceEquals(Current, elem); - } + public bool IsAtBookmark(Bookmark bookmark) => bookmark.data is ElementDefinition elem ? ReferenceEquals(Current, elem) : OrdinalPosition == null; public bool ReturnToBookmark(Bookmark bookmark) { @@ -329,8 +321,7 @@ public bool ReturnToBookmark(Bookmark bookmark) return true; } - var elem = bookmark.data as ElementDefinition; - if (elem == null) { return false; } + if (bookmark.data is not ElementDefinition elem) { return false; } // If the bookmark has an index, then try to use it var index = bookmark.index.GetValueOrDefault(-1); @@ -535,7 +526,7 @@ public List ToListOfElements() // public override string ToString() [DebuggerBrowsable(DebuggerBrowsableState.Never)] - string DebuggerDisplay + private string DebuggerDisplay { get { diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs index 94c68fdf2e..6cd849566e 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ProfileReference.cs @@ -6,6 +6,8 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable + using System; namespace Hl7.Fhir.Specification.Navigation @@ -13,37 +15,46 @@ namespace Hl7.Fhir.Specification.Navigation // [WMR 20160802] NEW /// Represents a reference to an element type profile. - /// Useful to parse complex profile references of the form "canonicalUrl#elementName". - internal struct ProfileReference + /// Useful to parse complex profile references of the form "canonicalUrl#Type.elementName". + internal class ProfileReference { - ProfileReference(string url) + private ProfileReference(string url) { - if (url == null) { throw new ArgumentNullException("url"); } - var pos = url.IndexOf('#'); - if (pos > 0 && pos < url.Length) + if (url == null) { throw new ArgumentNullException(nameof(url)); } + + var parts = url.Split('#'); + + if (parts.Length == 1) { - CanonicalUrl = url.Substring(0, pos); - ElementName = url.Substring(pos + 1); + // Just the canonical, no '#' present + CanonicalUrl = parts[0]; + ElementName = null; } else { - CanonicalUrl = url; - ElementName = null; + // There's a '#', so both or just the element are present + CanonicalUrl = parts[0].Length > 0 ? parts[0] : null; + ElementName = parts[1].Length > 0 ? parts[1] : null; } } /// Initialize a new instance from the specified url. /// A resource reference to a profile. /// A new structure. - public static ProfileReference Parse(string url) => new ProfileReference(url); + public static ProfileReference Parse(string url) => new(url); /// Returns the canonical url of the profile. - public string CanonicalUrl { get; } + public string? CanonicalUrl { get; } /// Returns an optional profile element name, if included in the reference. - public string ElementName { get; } + public string? ElementName { get; } /// Returns true if the profile reference includes an element name, false otherwise. - public bool IsComplex => ElementName != null; + public bool IsComplex => ElementName is not null; + + /// + /// Returns true of the profile reference includes a canonical url. + /// + public bool IsAbsolute => CanonicalUrl is not null; } } diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs index a86df395ac..c33616dda2 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs @@ -153,12 +153,10 @@ public IEnumerable Expand() return new[] { this }; else if (Current.Current.ContentReference != null) { - var name = Current.Current.ContentReference; - var reference = Current.ShallowCopy(); + if (!Current.TryFollowContentReference(s => Resolver.FindStructureDefinition(s), out var reference)) + throw new StructureDefinitionWalkerException($"The contentReference '{reference}' cannot be resolved."); - if (!reference.JumpToNameReference(name)) - throw new StructureDefinitionWalkerException($"Found a namereference '{name}' that cannot be resolved at '{Current.CanonicalPath()}'."); - return new[] { new StructureDefinitionWalker(reference, _resolver) }; + return new[] { new StructureDefinitionWalker(reference!, _resolver) }; } else if (Current.Current.Type.Count >= 1) { @@ -191,7 +189,7 @@ public IEnumerable OfType(string type) // types into their definitions: // 1) The root node of an SD for a type or constraint on the type => derive the base type // 2) At a non-root BackboneElement or Element => use the TypeRef.Type (After the expand, this can be only 1) - string? typeCanonical(ElementDefinitionNavigator nav) => + static string? typeCanonical(ElementDefinitionNavigator nav) => nav.Current.IsRootElement() ? nav.StructureDefinition.Type : nav.Current.Type.FirstOrDefault()?.Code; diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs index 368409cd4d..42f4b19027 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs @@ -82,7 +82,7 @@ public class MatchInfo public OperationOutcome.IssueComponent Issue; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - string DebuggerDisplay => $"B:{BaseBookmark.DebuggerDisplay} <-- {Action} --> D:{DiffBookmark.DebuggerDisplay}"; + private string DebuggerDisplay => $"B:{BaseBookmark.DebuggerDisplay} <-- {Action} --> D:{DiffBookmark.DebuggerDisplay}"; } /// Match the children of the current element in diffNav to the children of the current element in snapNav. @@ -144,7 +144,7 @@ public static List Match(ElementDefinitionNavigator snapNav, ElementD /// Move snapNav to matching base element for diffNav, if it exists. Otherwise diffNav introduces a new element. /// true if snapNav is positioned on macthing base element, or false if diffNav introduces a new element. - static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List choiceNames) + private static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List choiceNames) { // First, match directly -> try to find the child in base with the same name as the path in the diff if (StringComparer.Ordinal.Equals(snapNav.PathName, diffNav.PathName) || snapNav.MoveToNext(diffNav.PathName)) @@ -182,7 +182,7 @@ static bool matchBase(ElementDefinitionNavigator snapNav, ElementDefinitionNavig /// /// /// - static List constructMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) + private static List constructMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) { // [WMR 20160802] snapNav and diffNav point to matching elements // Determine the associated action (Add, Merge, Slice) @@ -204,7 +204,7 @@ static List constructMatch(ElementDefinitionNavigator snapNav, Elemen if (isChoice) { - if (!TypeIsSubSetOf(diffNav, snapNav)) + if (!typeIsSubSetOf(diffNav, snapNav)) { // diff.Types is not a subset of snap.Types, which is not allowed throw Error.InvalidOperation($"Internal error in snapshot generator ({nameof(ElementMatcher)}.{nameof(constructMatch)}): choice type of diff does not occur in snap, snap = '{snapNav.Path}', diff = '{diffNav.Path}'."); @@ -269,7 +269,7 @@ static List constructMatch(ElementDefinitionNavigator snapNav, Elemen return result; } - private static bool TypeIsSubSetOf(ElementDefinitionNavigator diffNav, ElementDefinitionNavigator snapNav) + private static bool typeIsSubSetOf(ElementDefinitionNavigator diffNav, ElementDefinitionNavigator snapNav) => !diffNav.Current.Type.Except(snapNav.Current.Type, new TypeRefEqualityComparer()).Any(); private class TypeRefEqualityComparer : IEqualityComparer @@ -292,7 +292,7 @@ public int GetHashCode(ElementDefinition.TypeRefComponent obj) } // [WMR 20160902] Represents a new element definition with no matching base element (for core resource & datatype definitions) - static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) + private static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav) { // Called by Match when the current diffNav does not match any following sibling of snapNav (base) // This happens when merging a core definition (e.g. Patient) with a base type (e.g. Resource) @@ -340,7 +340,7 @@ static MatchInfo constructNew(ElementDefinitionNavigator snapNav, ElementDefinit // However for sliced elements, we need to initialize the slice entry and all following named slices from the same base element. // Therefore we first clone the original, unmerged base element and it's children (recursively). // Now each slice match return a reference to the associated original base element, unaffected by further processing. - static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapNav) + private static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapNav) { var sliceBase = snapNav.CloneSubtree(); @@ -354,7 +354,7 @@ static ElementDefinitionNavigator initSliceBase(ElementDefinitionNavigator snapN return sliceBase; } - static List constructSliceMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, ElementDefinitionNavigator sliceBase = null) + private static List constructSliceMatch(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, ElementDefinitionNavigator sliceBase = null) { var result = new List(); @@ -540,9 +540,9 @@ private static ElementDefinitionNavigator returnToOriginalSliceBase(ElementDefin } /// Returns true if the element has type Extension and also specifies a custom type profile. - static bool isExtensionSlice(ElementDefinition element) => isExtensionSlice(element.Type.FirstOrDefault()); + private static bool isExtensionSlice(ElementDefinition element) => isExtensionSlice(element.Type.FirstOrDefault()); - static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) + private static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) => type != null && type.Code == FHIRAllTypes.Extension.GetLiteral() && type.Profile != null; @@ -550,7 +550,7 @@ static bool isExtensionSlice(ElementDefinition.TypeRefComponent type) // Match current snapshot and differential slice elements // Returns an initialized MatchInfo with action = Merge | Add // defaultBase represents the base element for newly introduced slices - static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, + private static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List discriminators, MatchInfo match) { Debug.Assert(match != null); // Caller should initialize match @@ -607,7 +607,7 @@ static void matchSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavi // Match current snapshot and differential extension slice elements on extension type profile // Returns an initialized MatchInfo with action = Merge | Add // defaultBase represents the base element for newly introduced slices - static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, + private static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, List discriminators, MatchInfo match) { // [WMR 20170110] Accept missing slicing component, e.g. to close the extension slice: Extension.extension { max = 0 } @@ -637,7 +637,7 @@ static void matchExtensionSlice(ElementDefinitionNavigator snapNav, ElementDefin // Match current snapshot and differential slice elements on @type = Element.Type.Code // Returns an initialized MatchInfo with action = Merge | Add - static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) + private static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) { var diffTypeCodes = diffNav.Current.Type.Select(t => t.Code).ToList(); if (diffTypeCodes.Count == 0) @@ -662,7 +662,7 @@ static void matchSliceByTypeCode(ElementDefinitionNavigator snapNav, ElementDefi // Match current snapshot and differential slice elements on @type|@profile = Element.Type.Code and Element.Type.Profile // Returns an initialized MatchInfo with action = Merge | Add - static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) + private static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementDefinitionNavigator diffNav, MatchInfo match) { matchSliceByTypeCode(snapNav, diffNav, match); if (match.Action == MatchAction.Merge) @@ -692,7 +692,7 @@ static void matchSliceByTypeProfile(ElementDefinitionNavigator snapNav, ElementD } /// Given an extension element, return the canonical url of the associated extension definition, or null. - static string getExtensionProfileUri(ElementDefinition elem) + private static string getExtensionProfileUri(ElementDefinition elem) { Debug.Assert(elem.IsExtension()); var elemTypes = elem.Type; @@ -703,13 +703,13 @@ static string getExtensionProfileUri(ElementDefinition elem) } /// Fixed default discriminator for slicing extension elements. - static readonly string UrlDiscriminator = "url"; + private static readonly string URLDISCRIMINATOR = "url"; /// Determines if the specified value equals the special predefined discriminator for slicing on element type profile. - static bool isProfileDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Profile; + private static bool isProfileDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Profile; /// Determines if the specified value equals the special predefined discriminator for slicing on element type. - static bool isTypeDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Type; + private static bool isTypeDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => discriminator?.Type == ElementDefinition.DiscriminatorType.Type; //EK: Commented out since this combination is not valid/has never been valid? In any case we did not consider it //when composing the new DiscriminatorType valueset. @@ -717,11 +717,11 @@ static string getExtensionProfileUri(ElementDefinition elem) //static bool isTypeAndProfileDiscriminator(string discriminator) => StringComparer.Ordinal.Equals(discriminator, TypeAndProfileDiscriminator); /// Determines if the specified value equals the fixed default discriminator for slicing extension elements. - static bool isUrlDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => StringComparer.Ordinal.Equals(discriminator?.Path, UrlDiscriminator); + private static bool isUrlDiscriminator(ElementDefinition.DiscriminatorComponent discriminator) => StringComparer.Ordinal.Equals(discriminator?.Path, URLDISCRIMINATOR); // [WMR 20160801] // Determine if the specified discriminator(s) match on (type and) profile - static bool isTypeProfileDiscriminator(IEnumerable discriminators) + private static bool isTypeProfileDiscriminator(IEnumerable discriminators) { // [WMR 20170411] TODO: update for STU3? @@ -746,7 +746,7 @@ static bool isTypeProfileDiscriminator(IEnumerableList names of all following choice type elements ('[x]'). - static List listChoiceElements(ElementDefinitionNavigator nav) + private static List listChoiceElements(ElementDefinitionNavigator nav) { var bm = nav.Bookmark(); var result = new List(); @@ -767,7 +767,7 @@ static List listChoiceElements(ElementDefinitionNavigator nav) /// Find name of child element that represent a rename of the specified choice type element name. /// An instance. /// Original choice type element name ending with "[x]". - static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) + private static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) { var bm = nav.Bookmark(); var result = new List(); @@ -792,7 +792,7 @@ static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string ch return null; } - static string previousElementName(ElementDefinitionNavigator nav) + private static string previousElementName(ElementDefinitionNavigator nav) { string result = null; diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index e61dc5aecb..755db7349c 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -31,9 +31,6 @@ // Detect and fix invalid non-null sliceNames on root elements #define FIX_SLICENAMES_ON_ROOT_ELEMENTS -// [WMR 20180409] Resolve contentReference from core resource/datatype (StructureDefinition.type) -#define FIX_CONTENTREFERENCE - using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; @@ -61,8 +58,8 @@ public sealed partial class SnapshotGenerator // TODO: Probably also need to share a common recursion stack... - readonly SnapshotGeneratorSettings _settings; - readonly SnapshotRecursionStack _stack = new SnapshotRecursionStack(); + private readonly SnapshotGeneratorSettings _settings; + private readonly SnapshotRecursionStack _stack = new(); /// /// Create a new instance of the class @@ -132,7 +129,7 @@ private static IAsyncResourceResolver verifySource(IAsyncResourceResolver resolv public SnapshotGeneratorSettings Settings => _settings; /// Returns a reference to the profile uri of the currently generating snapshot, or null. - string CurrentProfileUri => _stack.CurrentProfileUri; + private string CurrentProfileUri => _stack.CurrentProfileUri; /// /// (Re-)generate the component of the specified instance. @@ -480,7 +477,7 @@ void fixInvalidSliceNamesInSpecialization(StructureDefinition sd) #endif #if FIX_SLICENAMES_ON_ROOT_ELEMENTS - void fixInvalidSliceNameOnRootElement(ElementDefinition elem, StructureDefinition sd) + private void fixInvalidSliceNameOnRootElement(ElementDefinition elem, StructureDefinition sd) { if (elem != null) { @@ -522,24 +519,16 @@ private async T.Task expandElement(ElementDefinitionNavigator nav) if (!String.IsNullOrEmpty(defn.ContentReference)) { -#if FIX_CONTENTREFERENCE - // [WMR 20180409] NEW - // Resolve contentReference from core resource/datatype definition - // Specified by StructureDefinition.type == root element name - var coreStructure = await getStructureForContentReference(nav, true).ConfigureAwait(false); + // getStructureForContentReference emits issue if profile cannot be resolved if (coreStructure == null) { return false; } var sourceNav = ElementDefinitionNavigator.ForSnapshot(coreStructure); -#else - // [WMR 20180409] WRONG! - // Resolves contentReference from current StructureDef - // Recursive child items should NOT inherit constraints from parent in same profile - var sourceNav = new ElementDefinitionNavigator(nav); -#endif - if (!sourceNav.JumpToNameReference(defn.ContentReference)) + var profileRef = ProfileReference.Parse(defn.ContentReference); + + if (!sourceNav.JumpToNameReference("#" + profileRef.ElementName)) { addIssueInvalidNameReference(defn); return false; @@ -901,7 +890,7 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // [WMR 20170105] New: determine wether to expand the current element // Notify client to allow overriding the default behavior - bool mustExpandElement(ElementDefinitionNavigator diffNav) + private bool mustExpandElement(ElementDefinitionNavigator diffNav) { var hasChildren = diffNav.HasChildren; bool mustExpand = hasChildren; @@ -913,7 +902,7 @@ bool mustExpandElement(ElementDefinitionNavigator diffNav) /// /// /// Determines if the snapshot should inherit Element.id values from the differential. - void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) + private void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool mergeElementId) { // [WMR 20170421] Add parameter to control when (not) to inherit Element.id @@ -942,7 +931,7 @@ void mergeElementDefinition(ElementDefinition snap, ElementDefinition diff, bool // By default, use strategy (A): ignore custom type profile, merge from base // If mergeTypeProfiles is enabled, then first merge custom type profile before merging base - static readonly string DomainResource_Extension_Path = ModelInfo.FhirTypeToFhirTypeName(FHIRAllTypes.DomainResource) + ".extension"; + private static readonly string DomainResource_Extension_Path = ModelInfo.FhirTypeToFhirTypeName(FHIRAllTypes.DomainResource) + ".extension"; // Resolve the type profile of the currently selected element and merge into snapshot private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) @@ -1193,7 +1182,7 @@ private async T.Task mergeTypeProfiles(ElementDefinitionNavigator snap, El // However these properties are not constrained by the referencing profile itself, but inherited from the referenced extension definition. // So we actually should NOT emit these annotations on the referencing profile properties. // Call this method after merging an external extension definition to remove incorrect annotations from the target profile extension element - static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) + private static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) { if (IsEqualPath(elem.Base?.Path, DomainResource_Extension_Path)) { @@ -1208,7 +1197,7 @@ static void fixExtensionAnnotationsAfterMerge(ElementDefinition elem) /// Remove existing annotations, fix Base components /// // [WMR 20170501] OBSOLETE: notify listeners - moved to prepareTypeProfileChildren - bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typeNav) // , StructureDefinition typeStructure) + private bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typeNav) // , StructureDefinition typeStructure) { // [WMR 20170426] IMPORTANT! // Do NOT modify typeNav/typeStructure @@ -1269,7 +1258,7 @@ bool copyChildren(ElementDefinitionNavigator nav, ElementDefinitionNavigator typ /// For each element, raise the event /// and ensure that the element id is assigned. /// - void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, StructureDefinition typeProfile) + private void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, StructureDefinition typeProfile) { // Recursively re-generate IDs for elements inherited from external rebased type profile if (_settings.GenerateElementIds) @@ -1301,7 +1290,7 @@ void prepareMergedTypeProfileElements(ElementDefinitionNavigator snap, Structure // [WMR 20170713] NEW - for expandElementType // Raise OnPrepareElement event and provide matching base elements from typeNav // Cannot use prepareMergedTypeProfileElements, as the provided base element is incorrect in this case - void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav) + private void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav) { // Recursively re-generate IDs for elements inherited from external rebased type profile if (_settings.GenerateElementIds) @@ -1318,7 +1307,7 @@ void prepareExpandedTypeProfileElements(ElementDefinitionNavigator snap, Element } // [WMR 20170718] NEW - for addSlice - void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator sliceBase) + private void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavigator sliceBase) { if (MustRaisePrepareElement) { @@ -1328,7 +1317,7 @@ void prepareSliceElements(ElementDefinitionNavigator snap, ElementDefinitionNavi // Raise OnPrepareElement event for all elements in snap subtree // Recurse all elements and find matching base element in typeNav - void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav, bool prepareRoot) + private void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDefinitionNavigator typeNav, bool prepareRoot) { Debug.Assert(MustRaisePrepareElement); @@ -1417,7 +1406,7 @@ void prepareExpandedElementsInternal(ElementDefinitionNavigator snap, ElementDef } // Initialize [...].extension.url fixed url value, if missing - static void fixExtensionUrl(ElementDefinitionNavigator nav) + private static void fixExtensionUrl(ElementDefinitionNavigator nav) { // Case-insensitive comparison to match root "Extension" and child "extension" element if (StringComparer.OrdinalIgnoreCase.Equals("extension", nav.PathName) && nav.HasChildren) @@ -1549,7 +1538,7 @@ private async T.Task startSlice(ElementDefinitionNavigator snap, ElementDefiniti } } - static ElementDefinition getSliceLocation(ElementDefinitionNavigator diff, ElementDefinition location) + private static ElementDefinition getSliceLocation(ElementDefinitionNavigator diff, ElementDefinition location) { if (location == null) { @@ -1651,7 +1640,7 @@ private async T.Task addSlice(ElementDefinitionNavigator snap, ElementDefinition await mergeElement(snap, diff).ConfigureAwait(false); } - void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff, ElementDefinitionNavigator sliceBase) + private void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff, ElementDefinitionNavigator sliceBase) { var lastSlice = findSliceAddPosition(snap, diff); bool result = false; @@ -1684,7 +1673,7 @@ void addSliceBase(ElementDefinitionNavigator snap, ElementDefinitionNavigator di // Search snapshot slice group for suitable position to add new diff slice // If the snapshot contains a matching base slice element, then append after reslice group // Otherwise append after last slice - static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) + private static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDefinitionNavigator diff) { var bm = snap.Bookmark(); var name = snap.PathName; @@ -1717,8 +1706,7 @@ static Bookmark findSliceAddPosition(ElementDefinitionNavigator snap, ElementDef return result; } - - static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExtensionElement) + private static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExtensionElement) { // Create the slicing entry by cloning the base Extension element var elem = baseExtensionElement != null ? (ElementDefinition)baseExtensionElement.DeepCopy() : new ElementDefinition(); @@ -1743,7 +1731,7 @@ static ElementDefinition createExtensionSlicingEntry(ElementDefinition baseExten return elem; } - void markChangedByDiff(Element element) + private void markChangedByDiff(Element element) { if (_settings.GenerateExtensionsOnConstraints) { @@ -1809,7 +1797,7 @@ private async T.Task getStructureForTypeRef(ElementDefiniti /// An reference. /// A reference. /// A instance, or null. - private async static T.Task getStructureDefinitionForTypeCode(IAsyncResourceResolver resolver, FhirUri typeCodeElement) + private static async T.Task getStructureDefinitionForTypeCode(IAsyncResourceResolver resolver, FhirUri typeCodeElement) { StructureDefinition sd = null; var typeCode = typeCodeElement.Value; @@ -1829,9 +1817,6 @@ private async static T.Task getStructureDefinitionForTypeCo return sd; } - -#if FIX_CONTENTREFERENCE - // [WMR 20180410] Resolve the defining target structure of a contentReference private async T.Task getStructureForContentReference(ElementDefinitionNavigator nav, bool ensureSnapshot) { Debug.Assert(nav != null); @@ -1858,9 +1843,8 @@ private async T.Task getStructureForContentReference(Elemen return null; } -#endif - bool verifyStructure(StructureDefinition sd, string profileUrl, string location = null) + private bool verifyStructure(StructureDefinition sd, string profileUrl, string location = null) { if (sd == null) { @@ -2091,10 +2075,10 @@ private async T.Task getSnapshotRootElement(StructureDefiniti } /// Determine if the specified element paths are equal. Performs an ordinal comparison. - static bool IsEqualPath(string path, string other) => StringComparer.Ordinal.Equals(path, other); + private static bool IsEqualPath(string path, string other) => StringComparer.Ordinal.Equals(path, other); /// Determine if the specified element names are equal. Performs an ordinal comparison. - static bool IsEqualName(string name, string other) => StringComparer.Ordinal.Equals(name, other); + private static bool IsEqualName(string name, string other) => StringComparer.Ordinal.Equals(name, other); // [WMR 20170227] NEW // TODO: diff --git a/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs b/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs index 1ff00e1cdc..62d5e97de8 100644 --- a/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs +++ b/src/Hl7.Fhir.Specification/Specification/Source/ResourceResolverExtensions.cs @@ -23,7 +23,7 @@ public static class ResourceResolverExtensions [Obsolete("Using synchronous resolvers is not recommended anymore, use FindExtensionDefinitionAsync() instead.")] public static StructureDefinition FindExtensionDefinition(this IResourceResolver resolver, string uri) { - if (!(resolver.ResolveByCanonicalUri(uri) is StructureDefinition sd)) return null; + if (resolver.ResolveByCanonicalUri(uri) is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -38,7 +38,7 @@ public static StructureDefinition FindExtensionDefinition(this IResourceResolver /// Returns a StructureDefinition if it is resolvable and defines an extension, otherwise null. public static async T.Task FindExtensionDefinitionAsync(this IAsyncResourceResolver resolver, string uri) { - if (!(await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is StructureDefinition sd)) return null; + if (await resolver.ResolveByCanonicalUriAsync(uri).ConfigureAwait(false) is not StructureDefinition sd) return null; if (!sd.IsExtension) throw Error.Argument(nameof(uri), $"Found StructureDefinition at '{uri}', but is not an extension"); @@ -63,9 +63,7 @@ public static async T.Task FindStructureDefinitionAsync(thi public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, string typename) { var url = Uri.IsWellFormedUriString(typename, UriKind.Absolute) ? typename : ModelInfo.CanonicalUriForFhirCoreType(typename); -#pragma warning disable CS0618 // Type or member is obsolete return resolver.FindStructureDefinition(url); -#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -82,10 +80,7 @@ public static async T.Task FindStructureDefinitionForCoreTy /// [Obsolete("Using synchronous resolvers is not recommended anymore, use FindStructureDefinitionForCoreTypeAsync() instead.")] - public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, FHIRAllTypes type) -#pragma warning disable CS0618 // Type or member is obsolete - => resolver.FindStructureDefinitionForCoreType(ModelInfo.FhirTypeToFhirTypeName(type)); -#pragma warning restore CS0618 // Type or member is obsolete + public static StructureDefinition FindStructureDefinitionForCoreType(this IResourceResolver resolver, FHIRAllTypes type) => resolver.FindStructureDefinitionForCoreType(ModelInfo.FhirTypeToFhirTypeName(type)); /// /// Resolve the StructureDefinition for the FHIR-defined type given in . diff --git a/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs b/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs index a505536f16..ccf03d11f0 100644 --- a/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs +++ b/src/Hl7.Fhir.Specification/Specification/StructureDefinitionSummaryProvider.cs @@ -51,9 +51,9 @@ public async T.Task ProvideAsync(string canonical) } var sd = await _resolver.FindStructureDefinitionAsync(mappedCanonical).ConfigureAwait(false); - - return sd is null ? - null + + return sd is null ? + null : (IStructureDefinitionSummary)new StructureDefinitionComplexTypeSerializationInfo(ElementDefinitionNavigator.ForSnapshot(sd)); } @@ -175,7 +175,7 @@ internal ElementDefinitionSerializationInfo(ElementDefinitionNavigator nav) _definition = nav.Current; Order = nav.OrdinalPosition.Value; // cannot be null, since nav.Current != null - string noChoiceSuffix(string n) => n.EndsWith("[x]") ? n.Substring(0, n.Length - 3) : n; + static string noChoiceSuffix(string n) => n.EndsWith("[x]") ? n.Substring(0, n.Length - 3) : n; } private static ITypeSerializationInfo[] buildTypes(ElementDefinitionNavigator nav) @@ -186,6 +186,9 @@ private static ITypeSerializationInfo[] buildTypes(ElementDefinitionNavigator na { var reference = nav.ShallowCopy(); var name = nav.Current.ContentReference; + + // Note that these contentReferences MAY be absolute urls in profiles, but since this provider works on the + // core SDs only, we do not have to take this into account. if (!reference.JumpToNameReference(name)) throw Error.InvalidOperation($"StructureDefinition '{nav?.StructureDefinition?.Url}' " + $"has a namereference '{name}' on element '{nav.Current.Path}' that cannot be resolved."); @@ -210,21 +213,15 @@ public XmlRepresentation Representation { if (!_definition.Representation.Any()) return XmlRepresentation.XmlElement; - switch (_definition.Representation.First()) + return _definition.Representation.First() switch { - case ElementDefinition.PropertyRepresentation.XmlAttr: - return XmlRepresentation.XmlAttr; - case ElementDefinition.PropertyRepresentation.XmlText: - return XmlRepresentation.XmlText; - case ElementDefinition.PropertyRepresentation.TypeAttr: - return XmlRepresentation.TypeAttr; - case ElementDefinition.PropertyRepresentation.CdaText: - return XmlRepresentation.CdaText; - case ElementDefinition.PropertyRepresentation.Xhtml: - return XmlRepresentation.XHtml; - default: - return XmlRepresentation.XmlElement; - } + ElementDefinition.PropertyRepresentation.XmlAttr => XmlRepresentation.XmlAttr, + ElementDefinition.PropertyRepresentation.XmlText => XmlRepresentation.XmlText, + ElementDefinition.PropertyRepresentation.TypeAttr => XmlRepresentation.TypeAttr, + ElementDefinition.PropertyRepresentation.CdaText => XmlRepresentation.CdaText, + ElementDefinition.PropertyRepresentation.Xhtml => XmlRepresentation.XHtml, + _ => XmlRepresentation.XmlElement, + }; } } diff --git a/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs b/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs index c4c15c4155..6989b8400d 100644 --- a/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/ElementDefinitionNavigatorExtensions.cs @@ -6,8 +6,11 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +#nullable enable + using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; +using System; using System.Linq; namespace Hl7.Fhir.Validation @@ -45,5 +48,36 @@ internal static string ConstraintDescription(this ElementDefinition.ConstraintCo return desc; } + + /// + /// Resolve a the contentReference in a navigator and returns a navigator that is located on the target of the contentReference. + /// + /// The current navigator must be located at an element that contains a contentReference. + public static bool TryFollowContentReference(this ElementDefinitionNavigator sourceNavigator, Func resolver, out ElementDefinitionNavigator? targetNavigator) + { + targetNavigator = null; + + var reference = sourceNavigator.Current.ContentReference; + if (reference is null) return false; + + var profileRef = ProfileReference.Parse(reference); + + if (profileRef.IsAbsolute && profileRef.CanonicalUrl != sourceNavigator.StructureDefinition.Url) + { + // an external reference (e.g. http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item) + + var profile = resolver(profileRef.CanonicalUrl!); + if (profile is null) return false; + targetNavigator = ElementDefinitionNavigator.ForSnapshot(profile); + } + else + { + // a local reference + targetNavigator = sourceNavigator.ShallowCopy(); + } + + return targetNavigator.JumpToNameReference("#" + profileRef.ElementName); + } + } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification/Validation/Validator.cs b/src/Hl7.Fhir.Specification/Validation/Validator.cs index b04bc9ebe9..1ffdb5da6a 100644 --- a/src/Hl7.Fhir.Specification/Validation/Validator.cs +++ b/src/Hl7.Fhir.Specification/Validation/Validator.cs @@ -137,12 +137,13 @@ internal OperationOutcome ValidateInternal( return outcome; - StructureDefinition profileResolutionNeeded(string canonical) => + } + private StructureDefinition profileResolutionNeeded(string canonical) => //TODO: Need to make everything async in 2.x validator #pragma warning disable CS0618 // Type or member is obsolete Settings.ResourceResolver?.FindStructureDefinition(canonical); #pragma warning restore CS0618 // Type or member is obsolete - } + internal OperationOutcome ValidateInternal(ITypedElement instance, ElementDefinitionNavigator definition, ValidationState state) => ValidateInternal(instance, new[] { definition }, state).RemoveDuplicateMessages(); @@ -159,7 +160,7 @@ internal OperationOutcome ValidateInternal(ITypedElement elementNav, IEnumerable { var allDefinitions = definitions.ToList(); - if (allDefinitions.Count() == 1) + if (allDefinitions.Count == 1) outcome.Add(startValidation(allDefinitions.Single(), instance, state)); else { @@ -280,7 +281,7 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, outcome.Add(this.ValidateMinMaxValue(elementConstraints, instance)); outcome.Add(ValidateMaxLength(elementConstraints, instance)); outcome.Add(this.ValidateFp(definition.StructureDefinition.Url, elementConstraints, instance)); - outcome.Add(this.ValidateExtension(elementConstraints, instance, "http://hl7.org/fhir/StructureDefinition/regex")); + outcome.Add(this.validateExtension(elementConstraints, instance, "http://hl7.org/fhir/StructureDefinition/regex")); outcome.Add(this.ValidateBinding(elementConstraints, instance, context)); // If the report only has partial information, no use to show the hierarchy, so flatten it. @@ -296,7 +297,7 @@ private OperationOutcome validateElement(ElementDefinitionNavigator definition, } - private OperationOutcome ValidateExtension(IExtendable elementDef, ITypedElement instance, string uri) + private OperationOutcome validateExtension(IExtendable elementDef, ITypedElement instance, string uri) { var outcome = new OperationOutcome(); @@ -304,7 +305,7 @@ private OperationOutcome ValidateExtension(IExtendable elementDef, ITypedElement if (pattern != null) { var regex = new Regex(pattern); - var value = toStringRepresentation(instance); + var value = Validator.toStringRepresentation(instance); var success = Regex.Match(value, "^" + regex + "$").Success; if (!success) @@ -361,7 +362,7 @@ internal OperationOutcome ValidateBinding(ElementDefinition.ElementDefinitionBin ts = new LocalTerminologyService(Settings.ResourceResolver.AsAsync()); } - ValidationContext vc = new ValidationContext() { TerminologyService = ts }; + var vc = new ValidationContext() { TerminologyService = ts }; try { @@ -377,20 +378,19 @@ internal OperationOutcome ValidateBinding(ElementDefinition.ElementDefinitionBin } internal OperationOutcome ValidateNameReference( - ElementDefinition definition, - ElementDefinitionNavigator allDefinitions, + ElementDefinition _, + ElementDefinitionNavigator profile, ScopedNode instance, ValidationState state) { var outcome = new OperationOutcome(); + var definition = profile.Current; - if (definition.ContentReference != null) + if (profile.Current.ContentReference != null) { Trace(outcome, "Start validation of constraints referred to by nameReference '{0}'".FormatWith(definition.ContentReference), Issue.PROCESSING_PROGRESS, instance); - var referencedPositionNav = allDefinitions.ShallowCopy(); - - if (referencedPositionNav.JumpToNameReference(definition.ContentReference)) + if (profile.TryFollowContentReference(profileResolutionNeeded, out var referencedPositionNav)) outcome.Include(ValidateInternal(instance, referencedPositionNav, state)); else Trace(outcome, $"ElementDefinition uses a non-existing nameReference '{definition.ContentReference}'", Issue.PROFILE_ELEMENTDEF_INVALID_NAMEREFERENCE, instance); @@ -423,8 +423,8 @@ internal OperationOutcome VerifyPrimitiveContents(ElementDefinition definition, // type? Would it convert it to a .NET native type? How to check? // The spec has no regexes for the primitives mentioned below, so don't check them - return definition.Type.Count() == 1 - ? ValidateExtension(definition.Type.Single(), instance, "http://hl7.org/fhir/StructureDefinition/structuredefinition-regex") + return definition.Type.Count == 1 + ? validateExtension(definition.Type.Single(), instance, "http://hl7.org/fhir/StructureDefinition/structuredefinition-regex") : outcome; } @@ -471,12 +471,8 @@ internal OperationOutcome.IssueComponent Trace(OperationOutcome outcome, string : null; } - private string toStringRepresentation(ITypedElement vp) - { - return vp == null || vp.Value == null ? - null : - PrimitiveTypeConverter.ConvertTo(vp.Value); - } + private static string toStringRepresentation(ITypedElement vp) => + vp?.Value == null ? null : PrimitiveTypeConverter.ConvertTo(vp.Value); internal ITypedElement ExternalReferenceResolutionNeeded(string reference, OperationOutcome outcome, string path) { From b4376a069bf843f0b34b5081b99ce6d54685e063 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 20 Apr 2022 16:15:18 +0200 Subject: [PATCH 36/55] Fixing a few more messages --- .../Snapshot/SnapshotGeneratorTest.cs | 44 +++++++++---------- .../Navigation/ElementDefinitionNavigator.cs | 11 +---- .../Specification/Snapshot/ElementMatcher.cs | 5 +-- 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 80992316cf..fa4daf5663 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -3665,7 +3665,7 @@ public async T.Task TestInvalidProfileExtensionTarget() // - Patient.identifier:B/2 => Patient.identifier:B in MyPatient // - Patient.identifier:C => Patient.identifier in MyPatient - private static StructureDefinition SlicedPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3783,7 +3783,7 @@ public async T.Task TestSliceBase_SlicedPatient() Assert.AreEqual("2", nav.Current.Max); } - private static StructureDefinition NationalPatientProfile => new StructureDefinition() + private static StructureDefinition NationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -3806,7 +3806,7 @@ public async T.Task TestSliceBase_SlicedPatient() } }; - private static StructureDefinition SlicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition SlicedNationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -3998,7 +3998,7 @@ public async T.Task TestSliceBase_SlicedNationalPatient() #endif } - private static StructureDefinition ReslicedNationalPatientProfile => new StructureDefinition() + private static StructureDefinition ReslicedNationalPatientProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = "http://example.org/fhir/StructureDefinition/MyNationalPatient", @@ -4407,7 +4407,7 @@ private static void dumpMappings(IList mappi // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition PatientNonTypeSliceProfile => new StructureDefinition() + private static StructureDefinition PatientNonTypeSliceProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4452,7 +4452,7 @@ public async T.Task TestPatientNonTypeSlice() } // Ewout: type slices cannot contain renamed elements! - private static StructureDefinition ObservationSimpleQuantityProfile => new StructureDefinition() + private static StructureDefinition ObservationSimpleQuantityProfile => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -4613,7 +4613,7 @@ public async T.Task TestProfileConstraintsOnComplexExtensionChildren() // [WMR 20170424] For debugging ElementIdGenerator - private static StructureDefinition TestQuestionnaireProfile => new StructureDefinition() + private static StructureDefinition TestQuestionnaireProfile => new() { Type = FHIRAllTypes.Questionnaire.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Questionnaire), @@ -4787,7 +4787,7 @@ private static void assertElementIds(ElementDefinition elem, ElementDefinition b } - private static StructureDefinition TestPatientTypeSliceProfile => new StructureDefinition() + private static StructureDefinition TestPatientTypeSliceProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -4855,7 +4855,7 @@ public async T.Task TestElementIds_PatientWithTypeSlice() // [WMR 20170616] NEW - Test custom element IDs - private static StructureDefinition TestSlicedPatientWithCustomIdProfile => new StructureDefinition() + private static StructureDefinition TestSlicedPatientWithCustomIdProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5116,7 +5116,7 @@ private void dumpBaseDefId(StructureDefinition sd) private const string PatientIdentifierTypeValueSetUri = @"http://example.org/fhir/ValueSet/PatientIdentifierTypeValueSet"; // Identifier profile with valueset binding on child element Identifier.type - private static StructureDefinition PatientIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientIdentifierProfile => new() { Type = FHIRAllTypes.Identifier.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Identifier), @@ -5143,7 +5143,7 @@ private void dumpBaseDefId(StructureDefinition sd) // Patient profile with type profile constraint on Patient.identifier // Snapshot should pick up the valueset binding on Identifier.type - private static StructureDefinition PatientProfileWithIdentifierProfile => new StructureDefinition() + private static StructureDefinition PatientProfileWithIdentifierProfile => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5206,7 +5206,7 @@ public async T.Task TestTypeProfileWithChildElementBinding() Assert.IsFalse(nav.MoveToChild("type")); } - private static StructureDefinition QuestionnaireResponseWithSlice => new StructureDefinition() + private static StructureDefinition QuestionnaireResponseWithSlice => new() { Type = FHIRAllTypes.QuestionnaireResponse.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.QuestionnaireResponse), @@ -5312,7 +5312,7 @@ public async T.Task TestQRSliceChildrenBindings() // When expanding MyVitalSigns, the annotated base elements also include local diff constraints... WRONG! // As a result, Forge will not detect the existing local constraints (no yellow pen, excluded from output). - private static StructureDefinition MyDerivedObservation => new StructureDefinition() + private static StructureDefinition MyDerivedObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Observation), @@ -5376,7 +5376,7 @@ public async T.Task TestDerivedObservation() Assert.AreEqual(coreMethodElem.Short, baseElem.Short); } - private static StructureDefinition MyMoreDerivedObservation => new StructureDefinition() + private static StructureDefinition MyMoreDerivedObservation => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = MyDerivedObservation.Url, @@ -5458,7 +5458,7 @@ public async T.Task TestMoreDerivedObservation() } // [WMR 20170718] Test for slicing issue - private static StructureDefinition MySlicedDocumentReference => new StructureDefinition() + private static StructureDefinition MySlicedDocumentReference => new() { Type = FHIRAllTypes.Observation.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.DocumentReference), @@ -5553,7 +5553,7 @@ public async T.Task TestNamedSliceMinCardinality() // [WMR 20170718] NEW // Accept and handle derived profile constraints on existing slice entry in base profile - private static StructureDefinition MySlicedBasePatient => new StructureDefinition() + private static StructureDefinition MySlicedBasePatient => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5580,7 +5580,7 @@ public async T.Task TestNamedSliceMinCardinality() } }; - private static StructureDefinition MyMoreDerivedPatient => new StructureDefinition() + private static StructureDefinition MyMoreDerivedPatient => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = MySlicedBasePatient.Url, @@ -5680,7 +5680,7 @@ public async T.Task TestDosage() } } - private static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new StructureDefinition() + private static StructureDefinition MedicationStatementWithSimpleQuantitySlice => new() { Type = FHIRAllTypes.MedicationStatement.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.MedicationStatement), @@ -5756,7 +5756,7 @@ public async T.Task TestSimpleQuantitySlice() private const string SL_HumanNameTitleSuffixUri = @"http://example.org/fhir/StructureDefinition/SL-HumanNameTitleSuffix"; // Extension on complex datatype HumanName - private static StructureDefinition SL_HumanNameTitleSuffix => new StructureDefinition() + private static StructureDefinition SL_HumanNameTitleSuffix => new() { Type = FHIRAllTypes.Extension.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Extension), @@ -5789,7 +5789,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on complex datatype HumanName with extension element - private static StructureDefinition SL_HumanNameBasis => new StructureDefinition() + private static StructureDefinition SL_HumanNameBasis => new() { Type = FHIRAllTypes.HumanName.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.HumanName), @@ -5819,7 +5819,7 @@ public async T.Task TestSimpleQuantitySlice() }; // Profile on Patient referencing custom HumanName datatype profile - private static StructureDefinition SL_PatientBasis => new StructureDefinition() + private static StructureDefinition SL_PatientBasis => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = ModelInfo.CanonicalUriForFhirCoreType(FHIRAllTypes.Patient), @@ -5849,7 +5849,7 @@ public async T.Task TestSimpleQuantitySlice() private const string SL_NameSuffixValueSetUri = @"http://fhir.de/ValueSet/deuev/anlage-7-namenszusaetze"; // Derived profile on Patient - private static StructureDefinition SL_PatientDerived => new StructureDefinition() + private static StructureDefinition SL_PatientDerived => new() { Type = FHIRAllTypes.Patient.GetLiteral(), BaseDefinition = SL_PatientBasis.Url, diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index cb8e19576b..4aab5dd3b6 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -375,16 +375,7 @@ public bool InsertBefore(ElementDefinition sibling) return true; } - private bool canInsertSiblingHere() - { - // We're not positioned anywhere... - if (OrdinalPosition == null) return false; - - // Cannot insert a sibling to the unique root element - if (OrdinalPosition == 0) return false; - - return true; - } + private bool canInsertSiblingHere() => OrdinalPosition != null && OrdinalPosition != 0; /// diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs index 42f4b19027..b48ddc8724 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/ElementMatcher.cs @@ -770,7 +770,6 @@ private static List listChoiceElements(ElementDefinitionNavigator nav) private static string findRenamedChoiceElement(ElementDefinitionNavigator nav, string choiceName) { var bm = nav.Bookmark(); - var result = new List(); if (nav.MoveToFirstChild()) { @@ -832,7 +831,7 @@ public static void DumpMatches(this IEnumerable matche if (snapNav.Current != null && snapNav.Current.SliceName != null) bPos += $" '{snapNav.Current.SliceName}'"; if (diffNav.Current != null && diffNav.Current.SliceName != null) dPos += $" '{diffNav.Current.SliceName}'"; - Debug.WriteLine($"B:{bPos} <-- {match.Action.ToString()} --> D:{dPos}"); + Debug.WriteLine($"B:{bPos} <-- {match.Action} --> D:{dPos}"); } snapNav.ReturnToBookmark(sbm); @@ -857,7 +856,7 @@ public static void DumpMatch(this ElementMatcher.MatchInfo match, ElementDefinit if (snapNav.Current != null && snapNav.Current.SliceName != null) bPos += $" '{snapNav.Current.SliceName}'"; if (diffNav.Current != null && diffNav.Current.SliceName != null) dPos += $" '{diffNav.Current.SliceName}'"; - Debug.WriteLine($"B:{bPos} <-- {match.Action.ToString()} --> D:{dPos}"); + Debug.WriteLine($"B:{bPos} <-- {match.Action} --> D:{dPos}"); snapNav.ReturnToBookmark(sbm); diffNav.ReturnToBookmark(dbm); From a6d1dc7dc6d0ea78d9923ae2ea2ea8ac4e2d8919 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 20 Apr 2022 16:46:57 +0200 Subject: [PATCH 37/55] Use IssueComponent.Expression instead of IssueComponent.Location (is deprecated) --- common | 2 +- src/Hl7.Fhir.Core.Tests/Model/ModelTests.cs | 28 ++++++++----------- .../Snapshot/SnapshotGeneratorTest.cs | 4 +-- .../Validation/BasicValidationTests.cs | 8 +++--- src/Hl7.Fhir.Specification/Schema/Binding.cs | 8 +++--- .../FpConstraintValidationExtensions.cs | 2 +- .../Validation/TypeRefValidationExtensions.cs | 2 +- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/common b/common index daca988a75..f276363d78 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit daca988a7547659811754dfb3a6a51b6c55bd0a5 +Subproject commit f276363d78afd78dbde9b0f5136cd101f9a927fa diff --git a/src/Hl7.Fhir.Core.Tests/Model/ModelTests.cs b/src/Hl7.Fhir.Core.Tests/Model/ModelTests.cs index 89ea9ba674..f03e4b1918 100644 --- a/src/Hl7.Fhir.Core.Tests/Model/ModelTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Model/ModelTests.cs @@ -6,17 +6,13 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ +using FluentAssertions; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Text; using System.Collections.Generic; using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Hl7.Fhir.Model; using System.Xml.Linq; -using System.ComponentModel.DataAnnotations; -using Hl7.Fhir.Validation; -using Hl7.Fhir.Serialization; -using FluentAssertions; namespace Hl7.Fhir.Tests.Model { @@ -36,14 +32,14 @@ public void ValidateElementAssertions() } [TestMethod] - public void OperationOutcomeLocation() + public void OperationOutcomeExpression() { OperationOutcome oo = new OperationOutcome(); oo.Issue.Add(new OperationOutcome.IssueComponent() { - Location = new string[] { "yes" } + Expression = new string[] { "this" } }); - Assert.AreEqual(1, oo.Issue[0].Location.Count()); + Assert.AreEqual(1, oo.Issue[0].Expression.Count()); } [TestMethod] @@ -72,7 +68,7 @@ public void DateTimeHandling() [TestMethod] public void TestTryToDateTimeOffset() { - var fdt = new FhirDateTime(new DateTimeOffset(2021, 3, 18, 12, 22, 35, 999, new TimeSpan(-4, 0, 0))); + var fdt = new FhirDateTime(new DateTimeOffset(2021, 3, 18, 12, 22, 35, 999, new TimeSpan(-4, 0, 0))); Assert.AreEqual("2021-03-18T12:22:35.999-04:00", fdt.Value); Assert.IsTrue(fdt.TryToDateTimeOffset(out var dto1)); @@ -87,7 +83,7 @@ public void TestTryToDateTimeOffset() fdt = new FhirDateTime("2021-03-18T12:22:35.999Z"); Assert.IsTrue(fdt.TryToDateTimeOffset(out var dto3)); Assert.AreEqual("2021-03-18T12:22:35.9990000+00:00", dto3.ToString("o")); - + fdt = new FhirDateTime("2021-03-18T12:22:35.1234+04:00"); Assert.IsTrue(fdt.TryToDateTimeOffset(out var dto4)); Assert.AreEqual("2021-03-18T12:22:35.1234000+04:00", dto4.ToString("o")); @@ -100,7 +96,7 @@ public void TestTryToDateTimeOffset() [TestMethod] public void TodayTests() { - var todayLocal = Date.Today(); + var todayLocal = Date.Today(); Assert.AreEqual(DateTimeOffset.Now.ToString("yyy-MM-dd"), todayLocal.Value); var todayUtc = Date.UtcToday(); @@ -135,7 +131,7 @@ public void TestBundleLinkEncoding() var uriEncodedUrl = string.Format(urlFormat, Uri.EscapeDataString(param1), Uri.EscapeDataString(param2)); Assert.AreEqual(manuallyEncodedUrl, uriEncodedUrl); var uri = new Uri(manuallyEncodedUrl, UriKind.RelativeOrAbsolute); - var bundle = new Bundle {SelfLink = uri}; + var bundle = new Bundle { SelfLink = uri }; if (uri.IsAbsoluteUri) { Assert.AreEqual(uri.AbsoluteUri, bundle.SelfLink.AbsoluteUri); @@ -454,7 +450,7 @@ public void TestNamingSystemCanonical() Assert.IsNull(ns.UrlElement); ns.UniqueId.Add(new NamingSystem.UniqueIdComponent { Value = "http://nu.nl" }); - ns.UniqueId.Add(new NamingSystem.UniqueIdComponent { Value = "http://dan.nl", Preferred=true }); + ns.UniqueId.Add(new NamingSystem.UniqueIdComponent { Value = "http://dan.nl", Preferred = true }); Assert.AreEqual("http://dan.nl", ns.Url); Assert.AreEqual("http://dan.nl", ns.UrlElement.Value); @@ -621,7 +617,7 @@ public void TestCheckMinorVersionCompatibiliy() Assert.IsTrue(ModelInfo.CheckMinorVersionCompatibility("3.0.1")); Assert.IsTrue(ModelInfo.CheckMinorVersionCompatibility("3.0")); Assert.IsTrue(ModelInfo.CheckMinorVersionCompatibility("3.0.2")); - Assert.IsFalse(ModelInfo.CheckMinorVersionCompatibility("3")); + Assert.IsFalse(ModelInfo.CheckMinorVersionCompatibility("3")); } [TestMethod] diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 62300f555a..6ad1beeabc 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -1032,7 +1032,7 @@ private static void assertIssue(OperationOutcome.IssueComponent issue, Issue exp } if (location != null && location.Length > 0) { - Assert.IsTrue(location.SequenceEqual(issue.Location)); + Assert.IsTrue(location.SequenceEqual(issue.Expression)); } } @@ -1619,7 +1619,7 @@ private void dumpIssue(OperationOutcome.IssueComponent issue, int index) if (issue.Details.Text != null) sb.AppendFormat(" Text : '{0}'", issue.Details.Text); } if (issue.Diagnostics != null) { sb.AppendFormat(" Profile: '{0}'", issue.Diagnostics); } - if (issue.Location != null) { sb.AppendFormat(" Path: '{0}'", string.Join(" | ", issue.Location)); } + if (issue.Expression != null) { sb.AppendFormat(" Path: '{0}'", string.Join(" | ", issue.Expression)); } Debug.Print(sb.ToString()); } diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index d1172ee1d3..32ca12d59e 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -199,7 +199,7 @@ public void TestDuplicateOperationOutcomeIssues() { new OperationOutcome.IssueComponent { - Location = new string[]{"active.extension"}, + Expression = new string[]{"active.extension"}, Severity = OperationOutcome.IssueSeverity.Error, Details = new CodeableConcept { @@ -208,7 +208,7 @@ public void TestDuplicateOperationOutcomeIssues() }, new OperationOutcome.IssueComponent { - Location = new string[]{"active.extension"}, + Expression = new string[]{"active.extension"}, Severity = OperationOutcome.IssueSeverity.Error, Details = new CodeableConcept { @@ -224,7 +224,7 @@ public void TestDuplicateOperationOutcomeIssues() { new OperationOutcome.IssueComponent { - Location = new string[]{"active.value"}, + Expression = new string[]{"active.value"}, Severity = OperationOutcome.IssueSeverity.Error, Details = new CodeableConcept { @@ -233,7 +233,7 @@ public void TestDuplicateOperationOutcomeIssues() }, new OperationOutcome.IssueComponent { - Location = new string[]{"active.extension"}, + Expression = new string[]{"active.extension"}, Severity = OperationOutcome.IssueSeverity.Error, Details = new CodeableConcept { diff --git a/src/Hl7.Fhir.Specification/Schema/Binding.cs b/src/Hl7.Fhir.Specification/Schema/Binding.cs index 2be0f2b842..31c6322280 100644 --- a/src/Hl7.Fhir.Specification/Schema/Binding.cs +++ b/src/Hl7.Fhir.Specification/Schema/Binding.cs @@ -138,7 +138,7 @@ private async Task callService(ITerminologyService svc, string Coding coding = null, CodeableConcept cc = null, bool? abstractAllowed = null, string context = null) { try - { + { var parameters = new ValidateCodeParameters() .WithValueSet(canonical) .WithCode(code: code, system: system, display: display, context: context) @@ -149,14 +149,14 @@ private async Task callService(ITerminologyService svc, string var outcome = (await svc.ValueSetValidateCode(parameters).ConfigureAwait(false)).ToOperationOutcome(); - foreach (var issue in outcome.Issue) issue.Location = new string[] { location }; + foreach (var issue in outcome.Issue) issue.Expression = new string[] { location }; return outcome; } catch (FhirOperationException tse) { - string message = (cc?.Coding == null || cc.Coding.Count == 1) + string message = (cc?.Coding == null || cc.Coding.Count == 1) ? $"Terminology service failed while validating code '{code ?? coding?.Code ?? cc?.Coding[0]?.Code}' (system '{system ?? coding?.System ?? cc?.Coding[0]?.System}'): {tse.Message}" - : $"Terminology service failed while validating the codes: {tse.Message}"; + : $"Terminology service failed while validating the codes: {tse.Message}"; return Issue.TERMINOLOGY_SERVICE_FAILED .NewOutcomeWithIssue(message, location); diff --git a/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs index bee5e906c1..13024ccf9a 100644 --- a/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs @@ -72,7 +72,7 @@ public static OperationOutcome ValidateFp(this Validator v, string structureDefi Code = issue.Type, Details = issue.ToCodeableConcept(text), Diagnostics = constraintElement.GetFhirPathConstraint(), // Putting the fhirpath expression of the invariant in the diagnostics - Location = new string[] { instance.Location } + Expression = new string[] { instance.Location } }; outcomeIssue.Details.Coding.Add(new Coding(structureDefinitionUrl, constraintElement.Key, constraintElement.Human)); outcome.AddIssue(outcomeIssue); diff --git a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs index 0cf53de6e5..d15606ecc5 100644 --- a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs @@ -195,7 +195,7 @@ internal static OperationOutcome ValidateResourceReference( // Prefix each path with the referring resource's path to keep the locations // interpretable foreach (var issue in childResult.Issue) - issue.Location = issue.Location.Concat(new string[] { instance.Location }); + issue.Expression = issue.Expression.Concat(new string[] { instance.Location }); outcome.Include(childResult); } From 82b2ee3fc0d7b9955cfcc97c3e2b7bd1eb91fab8 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Thu, 21 Apr 2022 10:32:50 +0200 Subject: [PATCH 38/55] Made unit-tests work, added tests for new code. --- .../ProfileNavigationTest.cs | 44 ++++++++++++++++--- .../Navigation/ElementDefinitionNavigator.cs | 4 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs index 5f9aecdec2..93b517bc8f 100644 --- a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs @@ -10,7 +10,9 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Utility; +using Hl7.Fhir.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Collections.Generic; using System.Linq; @@ -24,7 +26,10 @@ public class ProfileNavigationTest [TestInitialize] public void Setup() { - _source = new CachedResolver(new DirectorySource("TestData/validation")); + _source = new CachedResolver( + new MultiResolver( + new DirectorySource("TestData/validation"), + ZipSource.CreateValidationSource())); } @@ -422,19 +427,48 @@ public void CopyChildTree() } [TestMethod] - public void LocateElementByName() + public void LocateContentReference() { var nav = createTestNav(); + nav.StructureDefinition = new StructureDefinition() { Url = "http://nu.nl/test" }; nav.JumpToFirst("A.B.C1.D"); - nav.Current.ContentReference = "A-Named-Constraint"; + nav.Current.ElementId = "A-Named-Constraint"; nav.Reset(); - Assert.IsTrue(nav.JumpToNameReference("A-Named-Constraint")); + Assert.IsTrue(nav.JumpToNameReference("#A-Named-Constraint")); Assert.AreEqual(7, nav.OrdinalPosition); - Assert.IsFalse(nav.JumpToNameReference("IDontExist")); + Assert.IsTrue(nav.JumpToNameReference("http://nu.nl/test#A-Named-Constraint")); + Assert.AreEqual(7, nav.OrdinalPosition); + + Assert.IsFalse(nav.JumpToNameReference("#IDontExist")); + + Assert.ThrowsException(() => + nav.JumpToNameReference("http://then.nl/test#A-Named-Constraint")); } + [TestMethod] + public void LocateExternalContentReference() + { + var nav = createTestNav(); + nav.StructureDefinition = new StructureDefinition() { Url = "http://nu.nl/test" }; + nav.JumpToFirst("A.B.C1.D"); + nav.Current.ContentReference = "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"; + + string lastHit = null; + + var qUrl = "http://hl7.org/fhir/StructureDefinition/Questionnaire"; + Assert.IsTrue(nav.TryFollowContentReference(resolver, out var target)); + Assert.AreEqual(lastHit, qUrl); + Assert.AreEqual(qUrl, target.StructureDefinition.Url); + Assert.AreEqual("Questionnaire.item", target.Current.Path); + + StructureDefinition resolver(string url) + { + lastHit = url; + return _source.FindStructureDefinition(url); + } + } [TestMethod] public void TestNodeDuplication() diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs index 4aab5dd3b6..05f6bc8da3 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/ElementDefinitionNavigator.cs @@ -90,7 +90,7 @@ public static ElementDefinitionNavigator ForDifferential(StructureDefinition sd) } /// Returns a reference to the instance containing the navigated elements. - public StructureDefinition StructureDefinition { get; private set; } + public StructureDefinition StructureDefinition { get; internal set; } /// Indicates if the navigator has not yet been positioned on a node. /// true if equals null, or false otherwise. @@ -292,7 +292,7 @@ public bool JumpToNameReference(string nameReference) for (int pos = 0; pos < Count; pos++) { - if (Elements[pos].ElementId == "#" + profileRef.ElementName) + if (Elements[pos].ElementId == profileRef.ElementName) { OrdinalPosition = pos; return true; From f2e45c8366f2d9300356784cbd6f01ba84691599 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 10:55:34 +0200 Subject: [PATCH 39/55] fix unit test and remove warnings --- src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs | 2 ++ .../Validation/BasicValidationTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs index 93b517bc8f..682301e55b 100644 --- a/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/ProfileNavigationTest.cs @@ -466,7 +466,9 @@ public void LocateExternalContentReference() StructureDefinition resolver(string url) { lastHit = url; +#pragma warning disable CS0618 // Type or member is obsolete return _source.FindStructureDefinition(url); +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs index bad4212a42..4c4eb796e0 100644 --- a/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs +++ b/src/Hl7.Fhir.Specification.Tests/Validation/BasicValidationTests.cs @@ -1297,7 +1297,7 @@ public void ValidateAbsoluteContentReferences() { new Questionnaire.ItemComponent() { - LinkId = "1", + LinkId = "1.1", Type = Questionnaire.QuestionnaireItemType.String } } From 5e194a8545f811ba1c492a5e1676a663b332bf14 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 11:19:04 +0200 Subject: [PATCH 40/55] create unit test for generating absolute contenReferences in snapshots --- .../Snapshot/SnapshotGeneratorTest.cs | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index fa4daf5663..9e19489e56 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7524,6 +7524,74 @@ public async T.Task CheckCardinalityOfProfiledType() sutCode.Min.Should().Be(sdCode.Min); } + [TestMethod] + public async T.Task TestAbsoluteContentReferenceGeneration() + { + //prepare + var zipSource = ZipSource.CreateValidationSource(); + var generator = new SnapshotGenerator(zipSource, SnapshotGeneratorSettings.CreateDefault()); + + + // Test if core resource has relative content references. + var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); + var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); + coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire#Questionnaire.item"); + + + //Create profile for testing creation of absolute references. + var profile = new StructureDefinition + { + Url = "http://firely-sdk.org/fhir/StructureDefinition/content-reference-check", + Status = PublicationStatus.Draft, + FhirVersion = "3.0.2", + Kind = StructureDefinition.StructureDefinitionKind.Resource, + Abstract = false, + Type = "Questionnaire", + BaseDefinition = "http://hl7.org/fhir/StructureDefinition/Questionnaire", + Derivation = StructureDefinition.TypeDerivationRule.Constraint, + Differential = new StructureDefinition.DifferentialComponent + { + Element = new List + { + new ElementDefinition + { + ElementId = "Questionnaire.item", + Path = "Questionnaire.item", + Slicing = new ElementDefinition.SlicingComponent + { + Discriminator = new List + { + new ElementDefinition.DiscriminatorComponent + { + Type = ElementDefinition.DiscriminatorType.Value, + Path = "type" + } + }, + Rules = ElementDefinition.SlicingRules.Open + } + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem", + Path = "Questionnaire.item", + SliceName = "booleanItem", + Min = 1 + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem.type", + Path = "Questionnaire.item.type", + Fixed = new Code("boolean") + } + } + } + }; + + // test if profiles have absolute content references. + var profileSnapshot = await generator.GenerateAsync(profile); + profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + } + [TestMethod] public async T.Task DiscriminatorBaseElementWithExpansionTest() { @@ -7534,14 +7602,14 @@ public async T.Task DiscriminatorBaseElementWithExpansionTest() var generator = new SnapshotGenerator(_testResolver, _settings); generator.PrepareElement += delegate (object _, SnapshotElementEventArgs e) - { - e.Element.Should().NotBeNull(); + { + e.Element.Should().NotBeNull(); - if (e.Element.Annotation() != null) - e.Element.RemoveAnnotations(); + if (e.Element.Annotation() != null) + e.Element.RemoveAnnotations(); - e.Element.AddAnnotation(new TestAnnotation(e.BaseStructure, e.BaseElement)); - }; + e.Element.AddAnnotation(new TestAnnotation(e.BaseStructure, e.BaseElement)); + }; var elements = await generator.GenerateAsync(sd); From c433e41d90c1c6acd384199c703421ca88d31083 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:32:22 +0200 Subject: [PATCH 41/55] expanded test --- .../Snapshot/SnapshotGeneratorTest.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 9e19489e56..a913f3155f 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7532,10 +7532,10 @@ public async T.Task TestAbsoluteContentReferenceGeneration() var generator = new SnapshotGenerator(zipSource, SnapshotGeneratorSettings.CreateDefault()); - // Test if core resource has relative content references. + //Test if core resource has relative content references. var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); - coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire#Questionnaire.item"); + coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire.item"); //Create profile for testing creation of absolute references. @@ -7582,6 +7582,12 @@ public async T.Task TestAbsoluteContentReferenceGeneration() ElementId = "Questionnaire.item:booleanItem.type", Path = "Questionnaire.item.type", Fixed = new Code("boolean") + }, + new ElementDefinition + { + ElementId = "Questionnaire.item:booleanItem.item.type", + Path = "Questionnaire.item.item.type", + Fixed = new Code("string") } } } @@ -7589,7 +7595,13 @@ public async T.Task TestAbsoluteContentReferenceGeneration() // test if profiles have absolute content references. var profileSnapshot = await generator.GenerateAsync(profile); - profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + + var cref1 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item").FirstOrDefault(); + cref1.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + + var cref2 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item.item").FirstOrDefault(); + cref2.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); + // profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); } [TestMethod] From 009785809ce219e30ca1c7078ef182e99bd107a9 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:32:47 +0200 Subject: [PATCH 42/55] make sure contentReferences for profiles are absolute --- .../Snapshot/SnapshotGeneratorTest.cs | 3 +- .../Snapshot/SnapshotGenerator.cs | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index a913f3155f..3b820b73ff 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7535,7 +7535,8 @@ public async T.Task TestAbsoluteContentReferenceGeneration() //Test if core resource has relative content references. var coreQuestionnaire = await _testResolver.FindStructureDefinitionAsync("http://hl7.org/fhir/StructureDefinition/Questionnaire"); var coreSnapshot = await generator.GenerateAsync(coreQuestionnaire); - coreSnapshot.Should().Contain(e => e.ContentReference == "#Questionnaire.item"); + var item = coreSnapshot.Where(e => e.Path == "Questionnaire.item.item").FirstOrDefault(); + item.ContentReference.Should().Be("#Questionnaire.item"); //Create profile for testing creation of absolute references. diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index 755db7349c..d00cb91790 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -399,6 +399,7 @@ private async T.Task> generate(StructureDefinition struc // [WMR 20170424] Inherit existing base components, generate if missing await ensureBaseComponents(snapshot.Element, structure.BaseDefinition, false).ConfigureAwait(false); + // [WMR 20170208] Moved to *AFTER* ensureBaseComponents - emits annotations... // [WMR 20160915] Derived profiles should never inherit the ChangedByDiff extension from the base structure snapshot.Element.RemoveAllConstrainedByDiffExtensions(); @@ -411,6 +412,8 @@ private async T.Task> generate(StructureDefinition struc } nav = new ElementDefinitionNavigator(snapshot.Element, structure); + + } else { @@ -419,6 +422,8 @@ private async T.Task> generate(StructureDefinition struc nav = new ElementDefinitionNavigator(snapshot.Element, structure); } + + // var nav = new ElementDefinitionNavigator(snapshot.Element); #if CACHE_ROOT_ELEMDEF @@ -879,6 +884,11 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // Now, recursively merge the children await merge(snap, diff).ConfigureAwait(false); + // [MS 20220425] Make sure newly added contentReferences (from bases, types, and contentReferences) are absolute. + // Except when the it's a core profile (Derivation is not a Constraint) + if (snap.StructureDefinition.Derivation == StructureDefinition.TypeDerivationRule.Constraint) + ensureAbsoluteContentReferences(snap, snap.StructureDefinition.BaseDefinition); + // [WMR 20160720] NEW // generate [...]extension.url/fixedUri if missing // Ewout: [...]extension.url may be missing from differential @@ -888,6 +898,34 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini } } + private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) + { + var bookmark = nav.Bookmark(); + + if (nav.MoveToFirstChild()) + { + do + { + if (!string.IsNullOrEmpty(nav.Current?.ContentReference)) + ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl); + } + while (nav.MoveToNext()); + } + + nav.ReturnToBookmark(bookmark); + } + + private static void ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) + { + if (contentReferenceElement.Value?.StartsWith("#") == true) + { + var contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : ModelInfo.FhirCoreProfileBaseUri.ToString() + baseTypeUrl; + contentReferenceElement.Value = contentRefBase + contentReferenceElement.Value; + } + } + + + // [WMR 20170105] New: determine wether to expand the current element // Notify client to allow overriding the default behavior private bool mustExpandElement(ElementDefinitionNavigator diffNav) From 6436da12dc3f9533b9f70b4a2a68215f0b25b08e Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 17:58:02 +0200 Subject: [PATCH 43/55] disables warning --- .../Specification/Navigation/StructureDefinitionWalker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs index c33616dda2..a3bdb1ff4a 100644 --- a/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs +++ b/src/Hl7.Fhir.Specification/Specification/Navigation/StructureDefinitionWalker.cs @@ -153,8 +153,10 @@ public IEnumerable Expand() return new[] { this }; else if (Current.Current.ContentReference != null) { +#pragma warning disable CS0618 // Type or member is obsolete if (!Current.TryFollowContentReference(s => Resolver.FindStructureDefinition(s), out var reference)) throw new StructureDefinitionWalkerException($"The contentReference '{reference}' cannot be resolved."); +#pragma warning restore CS0618 // Type or member is obsolete return new[] { new StructureDefinitionWalker(reference!, _resolver) }; } From da1d33fbd9132e6af7308a1ba415aa084011650d Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Mon, 25 Apr 2022 18:03:38 +0200 Subject: [PATCH 44/55] remove item group --- .../Hl7.Fhir.Specification.Tests.csproj | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj index f2782d1c1e..c72b9ec9ad 100644 --- a/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj +++ b/src/Hl7.Fhir.Specification.Tests/Hl7.Fhir.Specification.Tests.csproj @@ -32,46 +32,6 @@ PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 1ce31794932a1f3a935acf8fe96fcb9ab0254555 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Tue, 26 Apr 2022 10:23:40 +0200 Subject: [PATCH 45/55] Derive base of the contentReference from StructureDefinition.type --- .../Specification/Snapshot/SnapshotGenerator.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs index d00cb91790..5ea9005feb 100644 --- a/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs +++ b/src/Hl7.Fhir.Specification/Specification/Snapshot/SnapshotGenerator.cs @@ -887,7 +887,8 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini // [MS 20220425] Make sure newly added contentReferences (from bases, types, and contentReferences) are absolute. // Except when the it's a core profile (Derivation is not a Constraint) if (snap.StructureDefinition.Derivation == StructureDefinition.TypeDerivationRule.Constraint) - ensureAbsoluteContentReferences(snap, snap.StructureDefinition.BaseDefinition); + await ensureAbsoluteContentReferences(snap, snap.StructureDefinition.Type).ConfigureAwait(false); + // [WMR 20160720] NEW // generate [...]extension.url/fixedUri if missing @@ -898,7 +899,7 @@ private async T.Task mergeElement(ElementDefinitionNavigator snap, ElementDefini } } - private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) + private async T.Task ensureAbsoluteContentReferences(ElementDefinitionNavigator nav, string baseTypeUrl) { var bookmark = nav.Bookmark(); @@ -907,7 +908,7 @@ private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator n do { if (!string.IsNullOrEmpty(nav.Current?.ContentReference)) - ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl); + await ensureAbsoluteContentReference(nav.Current?.ContentReferenceElement, baseTypeUrl).ConfigureAwait(false); } while (nav.MoveToNext()); } @@ -915,15 +916,20 @@ private static void ensureAbsoluteContentReferences(ElementDefinitionNavigator n nav.ReturnToBookmark(bookmark); } - private static void ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) + private async T.Task ensureAbsoluteContentReference(FhirUri contentReferenceElement, string baseTypeUrl) { if (contentReferenceElement.Value?.StartsWith("#") == true) { - var contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : ModelInfo.FhirCoreProfileBaseUri.ToString() + baseTypeUrl; + string contentRefBase = baseTypeUrl.StartsWith("http://") ? baseTypeUrl : await getCanonicalUrlFromCoreType(baseTypeUrl).ConfigureAwait(false); contentReferenceElement.Value = contentRefBase + contentReferenceElement.Value; } } + private async T.Task getCanonicalUrlFromCoreType(string baseTypeUrl) + { + var coreType = await AsyncResolver.FindStructureDefinitionForCoreTypeAsync(baseTypeUrl).ConfigureAwait(false); + return coreType.Url; + } // [WMR 20170105] New: determine wether to expand the current element From 2ca2942cd7c32090b2ee386d972394548556157b Mon Sep 17 00:00:00 2001 From: brian_pos Date: Fri, 22 Apr 2022 14:15:33 +1000 Subject: [PATCH 46/55] Support providing the HttpClient itself to the FhirClient instead of just the HttpClientHandler (as another alternative) This then supports creating unit tests as described here for webapi based projects https://www.hanselman.com/blog/minimal-apis-in-net-6-but-where-are-the-unit-tests --- .../Rest/FhirClientTests.cs | 90 ++++++++++++++++++- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 33 ++++--- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs index 4aef5badcd..6821b98e7f 100644 --- a/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Core.Tests/Rest/FhirClientTests.cs @@ -1383,7 +1383,7 @@ public void CallsCallbacks() [TestMethod] [TestCategory("FhirClient"), TestCategory("IntegrationTest")] - public void CallsCallbacksHttpClient() + public void CallsCallbacksHttpClientHandler() { using (var handler = new HttpClientEventHandler()) { @@ -1468,6 +1468,94 @@ public void CallsCallbacksHttpClient() } } + [TestMethod] + [TestCategory("FhirClient"), TestCategory("IntegrationTest")] + public void CallsCallbacksHttpClient() + { + using (var handler = new HttpClientEventHandler()) + using (var httpClient = new HttpClient(handler)) + { + using (FhirClient client = new FhirClient(testEndpoint, httpClient: httpClient)) + { + client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + + bool calledBefore = false; + HttpStatusCode? status = null; + byte[] body = null; + byte[] bodyOut = null; + + handler.OnBeforeRequest += (sender, e) => + { + calledBefore = true; + bodyOut = e.Body; + }; + + handler.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/" + patientId); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); + + var bodyText = HttpUtil.DecodeBody(body, Encoding.UTF8); + + Assert.IsTrue(bodyText.Contains(" + { + calledBefore = true; + bodyOut = e.Body; + }; + + handler.OnAfterResponse += (sender, e) => + { + body = e.Body; + status = e.RawResponse.StatusCode; + }; + + var pat = client.Read("Patient/" + patientId); + Assert.IsTrue(calledBefore); + Assert.IsNotNull(status); + Assert.IsNotNull(body); + + var bodyText = HttpUtil.DecodeBody(body, Encoding.UTF8); + + Assert.IsTrue(bodyText.Contains(" @@ -27,7 +27,8 @@ public partial class FhirClient : BaseFhirClient /// If the endpoint does not end with a slash (/), it will be added. /// /// - /// If the messageHandler is provided then it must be disposed by the caller + /// If the messageHandler, or httpClient is provided then it must be disposed by the caller + /// Only one of the messageHandler or httpClient can be provided /// /// /// The URL of the server to connect to.
@@ -35,16 +36,27 @@ public partial class FhirClient : BaseFhirClient /// /// /// + /// /// - public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, IStructureDefinitionSummaryProvider provider = null) : base(endpoint, settings, provider) + public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, HttpClient httpClient = null, IStructureDefinitionSummaryProvider provider = null) : base(endpoint, settings, provider) { - // If user does not supply message handler, add decompression strategy in default handler. - var handler = messageHandler ?? new HttpClientHandler() + HttpClientRequester requester; + if (httpClient != null) { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; + if (messageHandler != null) throw new ArgumentException("Must not provide both messageHandler and httpClient parameters", nameof(messageHandler)); - var requester = new HttpClientRequester(Endpoint, Settings, handler, messageHandler == null); + requester = new HttpClientRequester(Endpoint, Settings, httpClient); + } + else + { + // If user does not supply message handler, create our own and add decompression strategy in default handler. + var handler = messageHandler ?? new HttpClientHandler() + { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; + + requester = new HttpClientRequester(Endpoint, Settings, handler, messageHandler == null); + } Requester = requester; // Expose default request headers to user. @@ -62,9 +74,10 @@ public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageH /// /// /// + /// /// - public FhirClient(string endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, IStructureDefinitionSummaryProvider provider = null) - : this(new Uri(endpoint), settings, messageHandler, provider) + public FhirClient(string endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, HttpClient httpClient = null, IStructureDefinitionSummaryProvider provider = null) + : this(new Uri(endpoint), settings, messageHandler, httpClient, provider) { } From 6dc665015032489f0e7f966639d080e5ba82dbbd Mon Sep 17 00:00:00 2001 From: brian_pos Date: Fri, 22 Apr 2022 22:44:13 +1000 Subject: [PATCH 47/55] Update the constructor based on the review feedback --- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 68 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index bde3877295..eef183a10c 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -6,7 +6,6 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE */ -using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; using Hl7.Fhir.Specification; using System; @@ -36,33 +35,45 @@ public partial class FhirClient : BaseFhirClient /// /// /// - /// /// - public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, HttpClient httpClient = null, IStructureDefinitionSummaryProvider provider = null) : base(endpoint, settings, provider) + public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, IStructureDefinitionSummaryProvider provider = null) : base(endpoint, settings, provider) { - HttpClientRequester requester; - if (httpClient != null) + // If user does not supply message handler, create our own and add decompression strategy in default handler. + var handler = messageHandler ?? new HttpClientHandler() { - if (messageHandler != null) throw new ArgumentException("Must not provide both messageHandler and httpClient parameters", nameof(messageHandler)); - - requester = new HttpClientRequester(Endpoint, Settings, httpClient); - } - else - { - // If user does not supply message handler, create our own and add decompression strategy in default handler. - var handler = messageHandler ?? new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate + }; - requester = new HttpClientRequester(Endpoint, Settings, handler, messageHandler == null); - } + HttpClientRequester requester = new HttpClientRequester(Endpoint, Settings, handler, messageHandler == null); Requester = requester; // Expose default request headers to user. RequestHeaders = requester.Client.DefaultRequestHeaders; } + /// + /// Creates a new client using a default endpoint + /// If the endpoint does not end with a slash (/), it will be added. + /// + /// + /// If the messageHandler, or httpClient is provided then it must be disposed by the caller + /// Only one of the messageHandler or httpClient can be provided + /// + /// + /// The URL of the server to connect to.
+ /// If the trailing '/' is not present, then it will be appended automatically + /// + /// + /// + /// + public FhirClient(Uri endpoint, HttpClient httpClient, FhirClientSettings settings = null, IStructureDefinitionSummaryProvider provider = null) : base(endpoint, settings, provider) + { + HttpClientRequester requester = new HttpClientRequester(Endpoint, Settings, httpClient); + Requester = requester; + + // Expose default request headers to user. + RequestHeaders = requester.Client.DefaultRequestHeaders; + } /// /// Creates a new client using a default endpoint @@ -74,10 +85,25 @@ public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageH /// /// /// + /// + public FhirClient(string endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, IStructureDefinitionSummaryProvider provider = null) + : this(new Uri(endpoint), settings, messageHandler, provider) + { + } + + /// + /// Creates a new client using a default endpoint + /// If the endpoint does not end with a slash (/), it will be added. + /// + /// + /// The URL of the server to connect to.
+ /// If the trailing '/' is not present, then it will be appended automatically + /// + /// /// /// - public FhirClient(string endpoint, FhirClientSettings settings = null, HttpMessageHandler messageHandler = null, HttpClient httpClient = null, IStructureDefinitionSummaryProvider provider = null) - : this(new Uri(endpoint), settings, messageHandler, httpClient, provider) + public FhirClient(string endpoint, HttpClient httpClient, FhirClientSettings settings = null, IStructureDefinitionSummaryProvider provider = null) + : this(new Uri(endpoint), httpClient, settings, provider) { } @@ -191,7 +217,7 @@ public ParserSettings ParserSettings } #endregion - [Obsolete ("OnBeforeRequest is deprecated, please add a HttpClientEventHandler or another HttpMessageHandler to the constructor to use this functionality", true)] + [Obsolete("OnBeforeRequest is deprecated, please add a HttpClientEventHandler or another HttpMessageHandler to the constructor to use this functionality", true)] public event EventHandler OnBeforeRequest; [Obsolete("OnAfterResponse is deprecated, please add a HttpClientEventHandler or another HttpMessageHandler to the constructor to use this functionality", true)] From 7a1f82e50bffce138ac53598d1d76e88b02d4e45 Mon Sep 17 00:00:00 2001 From: brian_pos Date: Tue, 26 Apr 2022 06:47:00 +1000 Subject: [PATCH 48/55] Update code comments --- src/Hl7.Fhir.Core/Rest/FhirClient.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Hl7.Fhir.Core/Rest/FhirClient.cs b/src/Hl7.Fhir.Core/Rest/FhirClient.cs index eef183a10c..12fa75d112 100644 --- a/src/Hl7.Fhir.Core/Rest/FhirClient.cs +++ b/src/Hl7.Fhir.Core/Rest/FhirClient.cs @@ -26,8 +26,7 @@ public partial class FhirClient : BaseFhirClient /// If the endpoint does not end with a slash (/), it will be added. ///
/// - /// If the messageHandler, or httpClient is provided then it must be disposed by the caller - /// Only one of the messageHandler or httpClient can be provided + /// If the messageHandler is provided then it must be disposed by the caller /// /// /// The URL of the server to connect to.
@@ -56,8 +55,7 @@ public FhirClient(Uri endpoint, FhirClientSettings settings = null, HttpMessageH /// If the endpoint does not end with a slash (/), it will be added. ///
/// - /// If the messageHandler, or httpClient is provided then it must be disposed by the caller - /// Only one of the messageHandler or httpClient can be provided + /// The httpClient must be disposed by the caller /// /// /// The URL of the server to connect to.
@@ -79,6 +77,9 @@ public FhirClient(Uri endpoint, HttpClient httpClient, FhirClientSettings settin /// Creates a new client using a default endpoint /// If the endpoint does not end with a slash (/), it will be added. ///
+ /// + /// If the messageHandler is provided then it must be disposed by the caller + /// /// /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically @@ -95,6 +96,9 @@ public FhirClient(string endpoint, FhirClientSettings settings = null, HttpMessa /// Creates a new client using a default endpoint /// If the endpoint does not end with a slash (/), it will be added. /// + /// + /// The httpClient must be disposed by the caller + /// /// /// The URL of the server to connect to.
/// If the trailing '/' is not present, then it will be appended automatically From 6b55ad62b3258b5d62af32f87a6550250c95a515 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 26 Apr 2022 11:21:06 +0200 Subject: [PATCH 49/55] Reference new common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index daca988a75..fbf6876613 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit daca988a7547659811754dfb3a6a51b6c55bd0a5 +Subproject commit fbf6876613e690202f30ff195c7244eb8719f35c From f54faa84abd57040b6d3ccd67c6726c9fed5c0c0 Mon Sep 17 00:00:00 2001 From: Marten Smits Date: Tue, 26 Apr 2022 11:32:20 +0200 Subject: [PATCH 50/55] remove comment --- .../Snapshot/SnapshotGeneratorTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs index 159e54516f..b3392920f5 100644 --- a/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs +++ b/src/Hl7.Fhir.Specification.Tests/Snapshot/SnapshotGeneratorTest.cs @@ -7662,7 +7662,6 @@ public async T.Task TestAbsoluteContentReferenceGeneration() var cref2 = profileSnapshot.Where(e => e.ElementId == "Questionnaire.item:booleanItem.item.item").FirstOrDefault(); cref2.ContentReference.Should().Be("http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); - // profileSnapshot.Should().Contain(e => e.ContentReference == "http://hl7.org/fhir/StructureDefinition/Questionnaire#Questionnaire.item"); } [TestMethod] From 34328df9451509d18b72b626e4a4ea9796c99063 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 28 Apr 2022 09:54:53 +0200 Subject: [PATCH 51/55] After review from Ewout --- common | 2 +- src/Hl7.Fhir.Specification/Schema/Binding.cs | 7 ++++++- .../Validation/FpConstraintValidationExtensions.cs | 4 +++- .../Validation/TypeRefValidationExtensions.cs | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/common b/common index f276363d78..03bd00b442 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit f276363d78afd78dbde9b0f5136cd101f9a927fa +Subproject commit 03bd00b4423a3d827774d7d41456630eca247a0e diff --git a/src/Hl7.Fhir.Specification/Schema/Binding.cs b/src/Hl7.Fhir.Specification/Schema/Binding.cs index 31c6322280..1874510204 100644 --- a/src/Hl7.Fhir.Specification/Schema/Binding.cs +++ b/src/Hl7.Fhir.Specification/Schema/Binding.cs @@ -149,7 +149,12 @@ private async Task callService(ITerminologyService svc, string var outcome = (await svc.ValueSetValidateCode(parameters).ConfigureAwait(false)).ToOperationOutcome(); - foreach (var issue in outcome.Issue) issue.Expression = new string[] { location }; + foreach (var issue in outcome.Issue) + { + issue.Expression = new string[] { location }; + // Location is deprecated, but we set this for backwards compatibility + issue.Location = new string[] { location }; + } return outcome; } catch (FhirOperationException tse) diff --git a/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs index 13024ccf9a..3f6485124b 100644 --- a/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/FpConstraintValidationExtensions.cs @@ -72,7 +72,9 @@ public static OperationOutcome ValidateFp(this Validator v, string structureDefi Code = issue.Type, Details = issue.ToCodeableConcept(text), Diagnostics = constraintElement.GetFhirPathConstraint(), // Putting the fhirpath expression of the invariant in the diagnostics - Expression = new string[] { instance.Location } + Expression = new string[] { instance.Location }, + // Location is deprecated, but we set this for backwards compatibility + Location = new string[] { instance.Location } }; outcomeIssue.Details.Coding.Add(new Coding(structureDefinitionUrl, constraintElement.Key, constraintElement.Human)); outcome.AddIssue(outcomeIssue); diff --git a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs index d15606ecc5..2bb8fa8365 100644 --- a/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs +++ b/src/Hl7.Fhir.Specification/Validation/TypeRefValidationExtensions.cs @@ -195,7 +195,11 @@ internal static OperationOutcome ValidateResourceReference( // Prefix each path with the referring resource's path to keep the locations // interpretable foreach (var issue in childResult.Issue) + { issue.Expression = issue.Expression.Concat(new string[] { instance.Location }); + // Location is deprecated, but we set this for backwards compatibility + issue.Location = issue.Expression.Concat(new string[] { instance.Location }); + } outcome.Include(childResult); } From 440e1705ec7f7e958ba69467addfdba44329142a Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 28 Apr 2022 12:36:57 +0200 Subject: [PATCH 52/55] Update common to latest version --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 03bd00b442..c285b9d083 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 03bd00b4423a3d827774d7d41456630eca247a0e +Subproject commit c285b9d083acdad92a0d935128cca3cb52370da4 From d4b86aaafc2075616ec75bfff4cd38fdf11d4756 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 28 Apr 2022 16:58:01 +0200 Subject: [PATCH 53/55] bumped version to 3.8.3 --- common | 2 +- src/firely-net-sdk.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common b/common index c285b9d083..a9d277274f 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit c285b9d083acdad92a0d935128cca3cb52370da4 +Subproject commit a9d277274ffb4fc9b8bbd08299fcff60f33f7e38 diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index bc5ce64a68..b806a3fc82 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -3,7 +3,7 @@ 3.8.3 - alpha + Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2021 Firely. Contains materials (C) HL7 International From 41b2953cc6b469c0abd21f22a367b6036e737dba Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 3 May 2022 16:53:12 +0200 Subject: [PATCH 54/55] Update common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 7f923d37bd..9c07c83165 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 7f923d37bd8489002a5614b78183c27d75d91c68 +Subproject commit 9c07c831652df64ca0795c19f24347c6b17ac161 From f8a7002e6638c8b7794249431b7fd8c7da9898fe Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 3 May 2022 17:23:51 +0200 Subject: [PATCH 55/55] Update common --- common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common b/common index 9c07c83165..59543b829d 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 9c07c831652df64ca0795c19f24347c6b17ac161 +Subproject commit 59543b829d14055ca456a527141cd7b9836322c8